diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fd38dc --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# WAVideoBox +秒级! 三行代码实现iOS视频压缩、变速、混音、合并、水印、旋转、换音、裁剪 ! 支持不同分辩率,支持你能想到的各种混合操作! + +============================= + +WAVideoBox是一款基于AVFoundation视频操作框架,用短短几行代码就可完成各种简单及至复杂的视频操作命令。使用简单,性能高超~ + +尤其是不同分辩率视频的组合操作,如,给A视频变速,给B视频加水印,把C视频旋转...把ABC..视频合并,再操作合并视频...循环... +用WAVideoBox能快速高效实现上述功能。 + +PS :支持多线程处理 + +===================== + +使用指导 + +常规操作直接append + 操作 + finish,各种骚操作组合请参照文尾 + +下列代码均跑于6s 12.0系统 + +===================== + +常规操作: 三行代码 + +// 压缩:将19秒的视频进行压缩, 耗时<1秒, 成果 : 6.7M -> 335KB + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox rotateVideoByDegress:90]; + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + // do it + }]; + +// 拼接:将两个不同分辨率视频拼接(17秒的视频), 耗时<3秒 ,如果是相同分辩率的视频耗时<1秒 + + [_videoBox appendVideoByPath:_testThreePath]; + [_videoBox appendVideoByPath:_testTwoPath]; + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + // do it + }]; + +// 混音:给视频混上其他视频/音乐的声音 (19秒视频), 耗时 < 1秒 + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox dubbedSoundBySoundPath:_testThreePath]; + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + // do it + }]; + +// 旋转、裁剪、换音、变速、水印....更多操作见demo + + +===================== + +组合操作 + +// 将1号拼接到video,用2号的音频替换,给视频加一个水印,旋转180度,混上3号的音,速度加快两倍 +// 把生好的视频裁6-12秒,压缩 +// 耗时 < 2秒 + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox appendVideoByPath:_testThreePath]; + [_videoBox replaceSoundBySoundPath:_testTwoPath]; + [_videoBox appendWaterMark:[UIImage imageNamed:@"waterMark"] relativeRect:CGRectMake(0.7, 0.7, 0.2, 0.1)]; + + [_videoBox rotateVideoByDegress:180]; + [_videoBox dubbedSoundBySoundPath:_testOnePath]; + [_videoBox gearBoxWithScale:2]; + + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(CMTimeMake(2400, 600), CMTimeMake(3600, 600))]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + // do it + }]; + + +===================== + +骚操作 + +// 放入原视频,换成1号的音,再把3号视频放入混音,剪其中8秒 +// 拼1号视频,给1号水印,剪其中8秒 +// 拼2号视频,给2号变速 +// 拼3号视频,旋转180,剪其中8秒 +// 把最后的视频再做一个变速 +// 耗时<3秒,如果都是分辩率一致的视频,将更快 + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox replaceSoundBySoundPath:_testOnePath]; + [_videoBox dubbedSoundBySoundPath:_testThreePath]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeMake(3600, 600))]; + + [_videoBox appendVideoByPath:_testOnePath]; + [_videoBox appendWaterMark:[UIImage imageNamed:@"waterMark"] relativeRect:CGRectMake(0.7, 0.7, 0.2, 0.12)]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(CMTimeMake(3600, 600), CMTimeMake(3600, 600))]; + + [_videoBox appendVideoByPath:_testTwoPath]; + [_videoBox gearBoxWithScale:2]; + + [_videoBox appendVideoByPath:_testThreePath]; + [_videoBox rotateVideoByDegress:180]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeMake(3600, 600))]; + + [_videoBox commit]; + [_videoBox gearBoxWithScale:2]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError * error) { + // do it + }]; + +===================== +Box分析 + + PS:为了灵活的调用、任意使用,效果与代码的调用顺序有关 + 比如先变速,再换音轨,明显后面才换的音轨不会有变速效果 + + WAVideoBox的工作区域: + ----缓存区,appedVideo后缓存区域 + ----工作区,视频指令区域,只有在这区域的视频才是有效操作 + ----合成区,完成视频指令后待合成区域 + + 1、appendVideo:会将视频加入到缓存区,将工作区内容合成一个视频(无法再拆散),并移到合成区,清空工作区 + 2、视频操作指令:缓存区视频放到工作区,视频操作只对工作区视频有效 + 3、commit:合成区域,将缓存区,合成区的视频移到工作区,视频操作对所有视频有效 + + tip:线程安全,适用于短视频处理 + + + diff --git a/WAVideoBox.xcodeproj/project.pbxproj b/WAVideoBox.xcodeproj/project.pbxproj new file mode 100644 index 0000000..6fd7cbe --- /dev/null +++ b/WAVideoBox.xcodeproj/project.pbxproj @@ -0,0 +1,702 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 3B9027CD21E1F2E7001B1497 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9027CC21E1F2E7001B1497 /* AppDelegate.m */; }; + 3B9027D021E1F2E7001B1497 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9027CF21E1F2E7001B1497 /* ViewController.m */; }; + 3B9027D321E1F2E7001B1497 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3B9027D121E1F2E7001B1497 /* Main.storyboard */; }; + 3B9027D521E1F2E9001B1497 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B9027D421E1F2E9001B1497 /* Assets.xcassets */; }; + 3B9027D821E1F2E9001B1497 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3B9027D621E1F2E9001B1497 /* LaunchScreen.storyboard */; }; + 3B9027DB21E1F2E9001B1497 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9027DA21E1F2E9001B1497 /* main.m */; }; + 3B9027E521E1F2E9001B1497 /* WAVideoBoxTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9027E421E1F2E9001B1497 /* WAVideoBoxTests.m */; }; + 3B9027F021E1F2E9001B1497 /* WAVideoBoxUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9027EF21E1F2E9001B1497 /* WAVideoBoxUITests.m */; }; + 3B90281921E1F2F3001B1497 /* WAVideoBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B9027FE21E1F2F3001B1497 /* WAVideoBox.m */; }; + 3B90281A21E1F2F3001B1497 /* WAAVSEImageMixCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90280321E1F2F3001B1497 /* WAAVSEImageMixCommand.m */; }; + 3B90281B21E1F2F3001B1497 /* WAAVSEGearboxCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90280521E1F2F3001B1497 /* WAAVSEGearboxCommand.m */; }; + 3B90281C21E1F2F3001B1497 /* WAAVSEGearboxCommandModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90280621E1F2F3001B1497 /* WAAVSEGearboxCommandModel.m */; }; + 3B90281D21E1F2F3001B1497 /* WAAVSERotateCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90280821E1F2F3001B1497 /* WAAVSERotateCommand.m */; }; + 3B90281E21E1F2F3001B1497 /* WAAVSECommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90280C21E1F2F3001B1497 /* WAAVSECommand.m */; }; + 3B90282021E1F2F3001B1497 /* WAAVSEExportCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90280E21E1F2F3001B1497 /* WAAVSEExportCommand.m */; }; + 3B90282121E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90281021E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.m */; }; + 3B90282221E1F2F3001B1497 /* WAAVSERangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90281221E1F2F3001B1497 /* WAAVSERangeCommand.m */; }; + 3B90282321E1F2F3001B1497 /* WAAVSEDubbedCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90281421E1F2F3001B1497 /* WAAVSEDubbedCommand.m */; }; + 3B90282421E1F2F3001B1497 /* WAAVSEComposition.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90281521E1F2F3001B1497 /* WAAVSEComposition.m */; }; + 3B90282521E1F2F3001B1497 /* WAAVSEVideoMixCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90281621E1F2F3001B1497 /* WAAVSEVideoMixCommand.m */; }; + 3B90282C21E1FAC3001B1497 /* PlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B90282B21E1FAC3001B1497 /* PlayViewController.m */; }; + 3B90283421E1FF22001B1497 /* test3.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 3B90282F21E1FF21001B1497 /* test3.mp4 */; }; + 3B90283821E1FF22001B1497 /* nature.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 3B90283321E1FF22001B1497 /* nature.mp4 */; }; + 3BCAF55621E72B8000663097 /* test2.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 3BCAF55521E72B8000663097 /* test2.mp4 */; }; + 3BD923AA21E44D8F007D85B8 /* test1.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 3BD923A921E44D8F007D85B8 /* test1.mp4 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3B9027E121E1F2E9001B1497 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B9027C021E1F2E7001B1497 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3B9027C721E1F2E7001B1497; + remoteInfo = WAVideoBox; + }; + 3B9027EC21E1F2E9001B1497 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3B9027C021E1F2E7001B1497 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3B9027C721E1F2E7001B1497; + remoteInfo = WAVideoBox; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 3B9027C821E1F2E7001B1497 /* WAVideoBox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WAVideoBox.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B9027CB21E1F2E7001B1497 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 3B9027CC21E1F2E7001B1497 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 3B9027CE21E1F2E7001B1497 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 3B9027CF21E1F2E7001B1497 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 3B9027D221E1F2E7001B1497 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 3B9027D421E1F2E9001B1497 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3B9027D721E1F2E9001B1497 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 3B9027D921E1F2E9001B1497 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3B9027DA21E1F2E9001B1497 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 3B9027E021E1F2E9001B1497 /* WAVideoBoxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WAVideoBoxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B9027E421E1F2E9001B1497 /* WAVideoBoxTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WAVideoBoxTests.m; sourceTree = ""; }; + 3B9027E621E1F2E9001B1497 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3B9027EB21E1F2E9001B1497 /* WAVideoBoxUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WAVideoBoxUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B9027EF21E1F2E9001B1497 /* WAVideoBoxUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WAVideoBoxUITests.m; sourceTree = ""; }; + 3B9027F121E1F2E9001B1497 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3B9027FE21E1F2F3001B1497 /* WAVideoBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAVideoBox.m; sourceTree = ""; }; + 3B90280021E1F2F3001B1497 /* WAAVSEExportCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEExportCommand.h; sourceTree = ""; }; + 3B90280221E1F2F3001B1497 /* WAAVSECommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSECommand.h; sourceTree = ""; }; + 3B90280321E1F2F3001B1497 /* WAAVSEImageMixCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEImageMixCommand.m; sourceTree = ""; }; + 3B90280421E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEReplaceSoundCommand.h; sourceTree = ""; }; + 3B90280521E1F2F3001B1497 /* WAAVSEGearboxCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEGearboxCommand.m; sourceTree = ""; }; + 3B90280621E1F2F3001B1497 /* WAAVSEGearboxCommandModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEGearboxCommandModel.m; sourceTree = ""; }; + 3B90280721E1F2F3001B1497 /* WAAVSERangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSERangeCommand.h; sourceTree = ""; }; + 3B90280821E1F2F3001B1497 /* WAAVSERotateCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSERotateCommand.m; sourceTree = ""; }; + 3B90280921E1F2F3001B1497 /* WAAVSEVideoMixCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEVideoMixCommand.h; sourceTree = ""; }; + 3B90280A21E1F2F3001B1497 /* WAAVSEComposition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEComposition.h; sourceTree = ""; }; + 3B90280B21E1F2F3001B1497 /* WAAVSEDubbedCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEDubbedCommand.h; sourceTree = ""; }; + 3B90280C21E1F2F3001B1497 /* WAAVSECommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSECommand.m; sourceTree = ""; }; + 3B90280E21E1F2F3001B1497 /* WAAVSEExportCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEExportCommand.m; sourceTree = ""; }; + 3B90280F21E1F2F3001B1497 /* WAAVSEGearboxCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEGearboxCommand.h; sourceTree = ""; }; + 3B90281021E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEReplaceSoundCommand.m; sourceTree = ""; }; + 3B90281121E1F2F3001B1497 /* WAAVSEImageMixCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEImageMixCommand.h; sourceTree = ""; }; + 3B90281221E1F2F3001B1497 /* WAAVSERangeCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSERangeCommand.m; sourceTree = ""; }; + 3B90281321E1F2F3001B1497 /* WAAVSEGearboxCommandModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSEGearboxCommandModel.h; sourceTree = ""; }; + 3B90281421E1F2F3001B1497 /* WAAVSEDubbedCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEDubbedCommand.m; sourceTree = ""; }; + 3B90281521E1F2F3001B1497 /* WAAVSEComposition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEComposition.m; sourceTree = ""; }; + 3B90281621E1F2F3001B1497 /* WAAVSEVideoMixCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WAAVSEVideoMixCommand.m; sourceTree = ""; }; + 3B90281721E1F2F3001B1497 /* WAAVSERotateCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAAVSERotateCommand.h; sourceTree = ""; }; + 3B90281821E1F2F3001B1497 /* WAVideoBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WAVideoBox.h; sourceTree = ""; }; + 3B90282A21E1FAC3001B1497 /* PlayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayViewController.h; sourceTree = ""; }; + 3B90282B21E1FAC3001B1497 /* PlayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlayViewController.m; sourceTree = ""; }; + 3B90282F21E1FF21001B1497 /* test3.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test3.mp4; sourceTree = ""; }; + 3B90283321E1FF22001B1497 /* nature.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = nature.mp4; sourceTree = ""; }; + 3BCAF55521E72B8000663097 /* test2.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test2.mp4; sourceTree = ""; }; + 3BD923A921E44D8F007D85B8 /* test1.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test1.mp4; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3B9027C521E1F2E7001B1497 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B9027DD21E1F2E9001B1497 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B9027E821E1F2E9001B1497 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3B9027BF21E1F2E7001B1497 = { + isa = PBXGroup; + children = ( + 3B9027CA21E1F2E7001B1497 /* WAVideoBox */, + 3B9027E321E1F2E9001B1497 /* WAVideoBoxTests */, + 3B9027EE21E1F2E9001B1497 /* WAVideoBoxUITests */, + 3B9027C921E1F2E7001B1497 /* Products */, + ); + sourceTree = ""; + }; + 3B9027C921E1F2E7001B1497 /* Products */ = { + isa = PBXGroup; + children = ( + 3B9027C821E1F2E7001B1497 /* WAVideoBox.app */, + 3B9027E021E1F2E9001B1497 /* WAVideoBoxTests.xctest */, + 3B9027EB21E1F2E9001B1497 /* WAVideoBoxUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 3B9027CA21E1F2E7001B1497 /* WAVideoBox */ = { + isa = PBXGroup; + children = ( + 3B90282E21E1FF16001B1497 /* Sources */, + 3B9027FD21E1F2F3001B1497 /* WAVideoBox */, + 3B9027CB21E1F2E7001B1497 /* AppDelegate.h */, + 3B9027CC21E1F2E7001B1497 /* AppDelegate.m */, + 3B9027CE21E1F2E7001B1497 /* ViewController.h */, + 3B9027CF21E1F2E7001B1497 /* ViewController.m */, + 3B90282A21E1FAC3001B1497 /* PlayViewController.h */, + 3B90282B21E1FAC3001B1497 /* PlayViewController.m */, + 3B9027D121E1F2E7001B1497 /* Main.storyboard */, + 3B9027D421E1F2E9001B1497 /* Assets.xcassets */, + 3B9027D621E1F2E9001B1497 /* LaunchScreen.storyboard */, + 3B9027D921E1F2E9001B1497 /* Info.plist */, + 3B9027DA21E1F2E9001B1497 /* main.m */, + ); + path = WAVideoBox; + sourceTree = ""; + }; + 3B9027E321E1F2E9001B1497 /* WAVideoBoxTests */ = { + isa = PBXGroup; + children = ( + 3B9027E421E1F2E9001B1497 /* WAVideoBoxTests.m */, + 3B9027E621E1F2E9001B1497 /* Info.plist */, + ); + path = WAVideoBoxTests; + sourceTree = ""; + }; + 3B9027EE21E1F2E9001B1497 /* WAVideoBoxUITests */ = { + isa = PBXGroup; + children = ( + 3B9027EF21E1F2E9001B1497 /* WAVideoBoxUITests.m */, + 3B9027F121E1F2E9001B1497 /* Info.plist */, + ); + path = WAVideoBoxUITests; + sourceTree = ""; + }; + 3B9027FD21E1F2F3001B1497 /* WAVideoBox */ = { + isa = PBXGroup; + children = ( + 3B90281821E1F2F3001B1497 /* WAVideoBox.h */, + 3B9027FE21E1F2F3001B1497 /* WAVideoBox.m */, + 3B9027FF21E1F2F3001B1497 /* WAAVSeCommand */, + ); + path = WAVideoBox; + sourceTree = ""; + }; + 3B9027FF21E1F2F3001B1497 /* WAAVSeCommand */ = { + isa = PBXGroup; + children = ( + 3B90280A21E1F2F3001B1497 /* WAAVSEComposition.h */, + 3B90281521E1F2F3001B1497 /* WAAVSEComposition.m */, + 3B90280221E1F2F3001B1497 /* WAAVSECommand.h */, + 3B90280C21E1F2F3001B1497 /* WAAVSECommand.m */, + 3B90280021E1F2F3001B1497 /* WAAVSEExportCommand.h */, + 3B90280E21E1F2F3001B1497 /* WAAVSEExportCommand.m */, + 3B90281121E1F2F3001B1497 /* WAAVSEImageMixCommand.h */, + 3B90280321E1F2F3001B1497 /* WAAVSEImageMixCommand.m */, + 3B90280421E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.h */, + 3B90281021E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.m */, + 3B90280F21E1F2F3001B1497 /* WAAVSEGearboxCommand.h */, + 3B90280521E1F2F3001B1497 /* WAAVSEGearboxCommand.m */, + 3B90281321E1F2F3001B1497 /* WAAVSEGearboxCommandModel.h */, + 3B90280621E1F2F3001B1497 /* WAAVSEGearboxCommandModel.m */, + 3B90280721E1F2F3001B1497 /* WAAVSERangeCommand.h */, + 3B90281221E1F2F3001B1497 /* WAAVSERangeCommand.m */, + 3B90281721E1F2F3001B1497 /* WAAVSERotateCommand.h */, + 3B90280821E1F2F3001B1497 /* WAAVSERotateCommand.m */, + 3B90280921E1F2F3001B1497 /* WAAVSEVideoMixCommand.h */, + 3B90281621E1F2F3001B1497 /* WAAVSEVideoMixCommand.m */, + 3B90280B21E1F2F3001B1497 /* WAAVSEDubbedCommand.h */, + 3B90281421E1F2F3001B1497 /* WAAVSEDubbedCommand.m */, + ); + path = WAAVSeCommand; + sourceTree = ""; + }; + 3B90282E21E1FF16001B1497 /* Sources */ = { + isa = PBXGroup; + children = ( + 3BD923A921E44D8F007D85B8 /* test1.mp4 */, + 3BCAF55521E72B8000663097 /* test2.mp4 */, + 3B90282F21E1FF21001B1497 /* test3.mp4 */, + 3B90283321E1FF22001B1497 /* nature.mp4 */, + ); + path = Sources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3B9027C721E1F2E7001B1497 /* WAVideoBox */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3B9027F421E1F2E9001B1497 /* Build configuration list for PBXNativeTarget "WAVideoBox" */; + buildPhases = ( + 3B9027C421E1F2E7001B1497 /* Sources */, + 3B9027C521E1F2E7001B1497 /* Frameworks */, + 3B9027C621E1F2E7001B1497 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WAVideoBox; + productName = WAVideoBox; + productReference = 3B9027C821E1F2E7001B1497 /* WAVideoBox.app */; + productType = "com.apple.product-type.application"; + }; + 3B9027DF21E1F2E9001B1497 /* WAVideoBoxTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3B9027F721E1F2E9001B1497 /* Build configuration list for PBXNativeTarget "WAVideoBoxTests" */; + buildPhases = ( + 3B9027DC21E1F2E9001B1497 /* Sources */, + 3B9027DD21E1F2E9001B1497 /* Frameworks */, + 3B9027DE21E1F2E9001B1497 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3B9027E221E1F2E9001B1497 /* PBXTargetDependency */, + ); + name = WAVideoBoxTests; + productName = WAVideoBoxTests; + productReference = 3B9027E021E1F2E9001B1497 /* WAVideoBoxTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 3B9027EA21E1F2E9001B1497 /* WAVideoBoxUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3B9027FA21E1F2E9001B1497 /* Build configuration list for PBXNativeTarget "WAVideoBoxUITests" */; + buildPhases = ( + 3B9027E721E1F2E9001B1497 /* Sources */, + 3B9027E821E1F2E9001B1497 /* Frameworks */, + 3B9027E921E1F2E9001B1497 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3B9027ED21E1F2E9001B1497 /* PBXTargetDependency */, + ); + name = WAVideoBoxUITests; + productName = WAVideoBoxUITests; + productReference = 3B9027EB21E1F2E9001B1497 /* WAVideoBoxUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3B9027C021E1F2E7001B1497 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = "黄锐灏"; + TargetAttributes = { + 3B9027C721E1F2E7001B1497 = { + CreatedOnToolsVersion = 10.1; + }; + 3B9027DF21E1F2E9001B1497 = { + CreatedOnToolsVersion = 10.1; + TestTargetID = 3B9027C721E1F2E7001B1497; + }; + 3B9027EA21E1F2E9001B1497 = { + CreatedOnToolsVersion = 10.1; + TestTargetID = 3B9027C721E1F2E7001B1497; + }; + }; + }; + buildConfigurationList = 3B9027C321E1F2E7001B1497 /* Build configuration list for PBXProject "WAVideoBox" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3B9027BF21E1F2E7001B1497; + productRefGroup = 3B9027C921E1F2E7001B1497 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3B9027C721E1F2E7001B1497 /* WAVideoBox */, + 3B9027DF21E1F2E9001B1497 /* WAVideoBoxTests */, + 3B9027EA21E1F2E9001B1497 /* WAVideoBoxUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3B9027C621E1F2E7001B1497 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B9027D821E1F2E9001B1497 /* LaunchScreen.storyboard in Resources */, + 3B9027D521E1F2E9001B1497 /* Assets.xcassets in Resources */, + 3B9027D321E1F2E7001B1497 /* Main.storyboard in Resources */, + 3B90283421E1FF22001B1497 /* test3.mp4 in Resources */, + 3BD923AA21E44D8F007D85B8 /* test1.mp4 in Resources */, + 3B90283821E1FF22001B1497 /* nature.mp4 in Resources */, + 3BCAF55621E72B8000663097 /* test2.mp4 in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B9027DE21E1F2E9001B1497 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B9027E921E1F2E9001B1497 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3B9027C421E1F2E7001B1497 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B9027D021E1F2E7001B1497 /* ViewController.m in Sources */, + 3B90282421E1F2F3001B1497 /* WAAVSEComposition.m in Sources */, + 3B90282221E1F2F3001B1497 /* WAAVSERangeCommand.m in Sources */, + 3B90281C21E1F2F3001B1497 /* WAAVSEGearboxCommandModel.m in Sources */, + 3B90282C21E1FAC3001B1497 /* PlayViewController.m in Sources */, + 3B90281E21E1F2F3001B1497 /* WAAVSECommand.m in Sources */, + 3B90281A21E1F2F3001B1497 /* WAAVSEImageMixCommand.m in Sources */, + 3B90282321E1F2F3001B1497 /* WAAVSEDubbedCommand.m in Sources */, + 3B90282121E1F2F3001B1497 /* WAAVSEReplaceSoundCommand.m in Sources */, + 3B9027DB21E1F2E9001B1497 /* main.m in Sources */, + 3B90281B21E1F2F3001B1497 /* WAAVSEGearboxCommand.m in Sources */, + 3B90281D21E1F2F3001B1497 /* WAAVSERotateCommand.m in Sources */, + 3B9027CD21E1F2E7001B1497 /* AppDelegate.m in Sources */, + 3B90282521E1F2F3001B1497 /* WAAVSEVideoMixCommand.m in Sources */, + 3B90281921E1F2F3001B1497 /* WAVideoBox.m in Sources */, + 3B90282021E1F2F3001B1497 /* WAAVSEExportCommand.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B9027DC21E1F2E9001B1497 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B9027E521E1F2E9001B1497 /* WAVideoBoxTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B9027E721E1F2E9001B1497 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B9027F021E1F2E9001B1497 /* WAVideoBoxUITests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3B9027E221E1F2E9001B1497 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3B9027C721E1F2E7001B1497 /* WAVideoBox */; + targetProxy = 3B9027E121E1F2E9001B1497 /* PBXContainerItemProxy */; + }; + 3B9027ED21E1F2E9001B1497 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3B9027C721E1F2E7001B1497 /* WAVideoBox */; + targetProxy = 3B9027EC21E1F2E9001B1497 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 3B9027D121E1F2E7001B1497 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 3B9027D221E1F2E7001B1497 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 3B9027D621E1F2E9001B1497 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 3B9027D721E1F2E9001B1497 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3B9027F221E1F2E9001B1497 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 3B9027F321E1F2E9001B1497 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3B9027F521E1F2E9001B1497 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 55RAX8ZP36; + INFOPLIST_FILE = WAVideoBox/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.doudle.videoBox.WAVideoBox; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3B9027F621E1F2E9001B1497 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 55RAX8ZP36; + INFOPLIST_FILE = WAVideoBox/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.doudle.videoBox.WAVideoBox; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 3B9027F821E1F2E9001B1497 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 55RAX8ZP36; + INFOPLIST_FILE = WAVideoBoxTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.doudle.videoBox.WAVideoBoxTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WAVideoBox.app/WAVideoBox"; + }; + name = Debug; + }; + 3B9027F921E1F2E9001B1497 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 55RAX8ZP36; + INFOPLIST_FILE = WAVideoBoxTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.doudle.videoBox.WAVideoBoxTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WAVideoBox.app/WAVideoBox"; + }; + name = Release; + }; + 3B9027FB21E1F2E9001B1497 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 55RAX8ZP36; + INFOPLIST_FILE = WAVideoBoxUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.doudle.videoBox.WAVideoBoxUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = WAVideoBox; + }; + name = Debug; + }; + 3B9027FC21E1F2E9001B1497 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 55RAX8ZP36; + INFOPLIST_FILE = WAVideoBoxUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.doudle.videoBox.WAVideoBoxUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = WAVideoBox; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3B9027C321E1F2E7001B1497 /* Build configuration list for PBXProject "WAVideoBox" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3B9027F221E1F2E9001B1497 /* Debug */, + 3B9027F321E1F2E9001B1497 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3B9027F421E1F2E9001B1497 /* Build configuration list for PBXNativeTarget "WAVideoBox" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3B9027F521E1F2E9001B1497 /* Debug */, + 3B9027F621E1F2E9001B1497 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3B9027F721E1F2E9001B1497 /* Build configuration list for PBXNativeTarget "WAVideoBoxTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3B9027F821E1F2E9001B1497 /* Debug */, + 3B9027F921E1F2E9001B1497 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3B9027FA21E1F2E9001B1497 /* Build configuration list for PBXNativeTarget "WAVideoBoxUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3B9027FB21E1F2E9001B1497 /* Debug */, + 3B9027FC21E1F2E9001B1497 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3B9027C021E1F2E7001B1497 /* Project object */; +} diff --git a/WAVideoBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/WAVideoBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..3cdc1a5 --- /dev/null +++ b/WAVideoBox.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/WAVideoBox.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/WAVideoBox.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/WAVideoBox.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/WAVideoBox.xcodeproj/project.xcworkspace/xcuserdata/huangruihao.xcuserdatad/UserInterfaceState.xcuserstate b/WAVideoBox.xcodeproj/project.xcworkspace/xcuserdata/huangruihao.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..5d963e8 Binary files /dev/null and b/WAVideoBox.xcodeproj/project.xcworkspace/xcuserdata/huangruihao.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/WAVideoBox.xcodeproj/xcuserdata/huangruihao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/WAVideoBox.xcodeproj/xcuserdata/huangruihao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..8f94031 --- /dev/null +++ b/WAVideoBox.xcodeproj/xcuserdata/huangruihao.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/WAVideoBox.xcodeproj/xcuserdata/huangruihao.xcuserdatad/xcschemes/xcschememanagement.plist b/WAVideoBox.xcodeproj/xcuserdata/huangruihao.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..2c677bb --- /dev/null +++ b/WAVideoBox.xcodeproj/xcuserdata/huangruihao.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + WAVideoBox.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/WAVideoBox/AppDelegate.h b/WAVideoBox/AppDelegate.h new file mode 100644 index 0000000..aa6de35 --- /dev/null +++ b/WAVideoBox/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/WAVideoBox/AppDelegate.m b/WAVideoBox/AppDelegate.m new file mode 100644 index 0000000..b860963 --- /dev/null +++ b/WAVideoBox/AppDelegate.m @@ -0,0 +1,52 @@ +// +// AppDelegate.m +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + // Override point for customization after application launch. + return YES; +} + + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. +} + + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. +} + + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + + +@end diff --git a/WAVideoBox/Assets.xcassets/AppIcon.appiconset/Contents.json b/WAVideoBox/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d8db8d6 --- /dev/null +++ b/WAVideoBox/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WAVideoBox/Assets.xcassets/Contents.json b/WAVideoBox/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/WAVideoBox/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WAVideoBox/Assets.xcassets/waterMark.imageset/Contents.json b/WAVideoBox/Assets.xcassets/waterMark.imageset/Contents.json new file mode 100644 index 0000000..d6bbfac --- /dev/null +++ b/WAVideoBox/Assets.xcassets/waterMark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "camera_filter3@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "camera_filter3@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/WAVideoBox/Assets.xcassets/waterMark.imageset/camera_filter3@2x.png b/WAVideoBox/Assets.xcassets/waterMark.imageset/camera_filter3@2x.png new file mode 100644 index 0000000..d73f6f4 Binary files /dev/null and b/WAVideoBox/Assets.xcassets/waterMark.imageset/camera_filter3@2x.png differ diff --git a/WAVideoBox/Assets.xcassets/waterMark.imageset/camera_filter3@3x.png b/WAVideoBox/Assets.xcassets/waterMark.imageset/camera_filter3@3x.png new file mode 100644 index 0000000..8ba4de3 Binary files /dev/null and b/WAVideoBox/Assets.xcassets/waterMark.imageset/camera_filter3@3x.png differ diff --git a/WAVideoBox/Base.lproj/LaunchScreen.storyboard b/WAVideoBox/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..bfa3612 --- /dev/null +++ b/WAVideoBox/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WAVideoBox/Base.lproj/Main.storyboard b/WAVideoBox/Base.lproj/Main.storyboard new file mode 100644 index 0000000..67db90e --- /dev/null +++ b/WAVideoBox/Base.lproj/Main.storyboard @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WAVideoBox/Info.plist b/WAVideoBox/Info.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/WAVideoBox/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/WAVideoBox/PlayViewController.h b/WAVideoBox/PlayViewController.h new file mode 100644 index 0000000..c47454b --- /dev/null +++ b/WAVideoBox/PlayViewController.h @@ -0,0 +1,19 @@ +// +// PlayViewController.h +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface PlayViewController : UIViewController + +- (void)loadWithFilePath:(NSString *)filePath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WAVideoBox/PlayViewController.m b/WAVideoBox/PlayViewController.m new file mode 100644 index 0000000..5841835 --- /dev/null +++ b/WAVideoBox/PlayViewController.m @@ -0,0 +1,43 @@ +// +// PlayViewController.m +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import "PlayViewController.h" +#import + +@interface PlayViewController () + +@property(nonatomic,strong) AVPlayerViewController *playerController; +@property (nonatomic , strong) NSString *filePath; + +@end + +@implementation PlayViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + _playerController = [[AVPlayerViewController alloc] init]; + NSURL * url = [NSURL fileURLWithPath:self.filePath]; + _playerController.player = [AVPlayer playerWithURL:url]; + _playerController.view.frame = self.view.bounds; + _playerController.showsPlaybackControls = YES; + + [self.view addSubview:_playerController.view]; + +} + +- (void)viewDidAppear:(BOOL)animated{ + [super viewDidAppear:animated]; + [[_playerController player] play]; +} + +- (void)loadWithFilePath:(NSString *)filePath{ + self.filePath = filePath; +} + + +@end diff --git a/WAVideoBox/Sources/nature.mp4 b/WAVideoBox/Sources/nature.mp4 new file mode 100644 index 0000000..db2a781 Binary files /dev/null and b/WAVideoBox/Sources/nature.mp4 differ diff --git a/WAVideoBox/Sources/test1.mp4 b/WAVideoBox/Sources/test1.mp4 new file mode 100644 index 0000000..6b168f2 Binary files /dev/null and b/WAVideoBox/Sources/test1.mp4 differ diff --git a/WAVideoBox/Sources/test2.mp4 b/WAVideoBox/Sources/test2.mp4 new file mode 100644 index 0000000..368face Binary files /dev/null and b/WAVideoBox/Sources/test2.mp4 differ diff --git a/WAVideoBox/Sources/test3.mp4 b/WAVideoBox/Sources/test3.mp4 new file mode 100644 index 0000000..e646182 Binary files /dev/null and b/WAVideoBox/Sources/test3.mp4 differ diff --git a/WAVideoBox/ViewController.h b/WAVideoBox/ViewController.h new file mode 100644 index 0000000..c2fb2b5 --- /dev/null +++ b/WAVideoBox/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/WAVideoBox/ViewController.m b/WAVideoBox/ViewController.m new file mode 100644 index 0000000..69e6285 --- /dev/null +++ b/WAVideoBox/ViewController.m @@ -0,0 +1,271 @@ +// +// ViewController.m +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import "ViewController.h" +#import "WAVideoBox.h" +#import "PlayViewController.h" +@interface ViewController () + +@property (nonatomic , copy) NSString *videoPath; + +@property (nonatomic , copy) NSString *testOnePath; + +@property (nonatomic , copy) NSString *testTwoPath; + +@property (nonatomic , copy) NSString *testThreePath; + +@property (nonatomic , strong) WAVideoBox *videoBox; + + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + _videoBox = [WAVideoBox new]; + + _videoPath = [[NSBundle mainBundle] pathForResource:@"nature.mp4" ofType:nil]; + + _testOnePath = [[NSBundle mainBundle] pathForResource:@"test1.mp4" ofType:nil]; + _testTwoPath = [[NSBundle mainBundle] pathForResource:@"test2.mp4" ofType:nil]; + _testThreePath = [[NSBundle mainBundle] pathForResource:@"test3.mp4" ofType:nil]; + +} + +#pragma mari private method + +- (NSString *)buildFilePath{ + + return [NSTemporaryDirectory() stringByAppendingString:[NSString stringWithFormat:@"%f.mp4", [[NSDate date] timeIntervalSinceReferenceDate]]]; +} + +- (void)goToPlayVideoByFilePath:(NSString *)filePath{ + PlayViewController *playVc = [PlayViewController new]; + [playVc loadWithFilePath:filePath]; + [self.navigationController pushViewController:playVc animated:YES]; +} + +#pragma mark 常规操作 +- (IBAction)rangeVideo:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(CMTimeMake(3600, 600), CMTimeMake(3600, 600))]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; +} + +- (IBAction)compressVideo:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + _videoBox.ratio = WAVideoExportRatioLowQuality; +// _videoBox.videoQuality = 1; 有两种方法可以压缩 + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + wself.videoBox.ratio = WAVideoExportRatio960x540; + wself.videoBox.videoQuality = 0; + }]; +} + +- (IBAction)addWaterMark:(id)sender { + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox appendWaterMark:[UIImage imageNamed:@"waterMark"] relativeRect:CGRectMake(0.7, 0.7, 0.2, 0.12)]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; +} + +- (IBAction)rotateVideo:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox rotateVideoByDegress:90]; + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; +} + +- (IBAction)replaceVideo:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox replaceSoundBySoundPath:_testOnePath]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; +} + +- (IBAction)mixVideo:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_testThreePath]; + [_videoBox appendVideoByPath:_testTwoPath]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; +} + +- (IBAction)mixSound:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox dubbedSoundBySoundPath:_testThreePath]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; + +} + +- (IBAction)gearVideo:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox gearBoxWithScale:3]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; +} + + +#pragma mark 混合操作 +- (IBAction)composeEdit:(id)sender { + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + // 将1号拼接到video,用2号的音频替换,给视频加一个水印,旋转180度,混上3号的音,速度加快两倍,把生好的视频裁6-12秒,压缩 + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox appendVideoByPath:_testThreePath]; + [_videoBox replaceSoundBySoundPath:_testTwoPath]; + [_videoBox appendWaterMark:[UIImage imageNamed:@"waterMark"] relativeRect:CGRectMake(0.7, 0.7, 0.2, 0.1)]; + + [_videoBox rotateVideoByDegress:180]; + [_videoBox dubbedSoundBySoundPath:_testOnePath]; + [_videoBox gearBoxWithScale:2]; + + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(CMTimeMake(2400, 600), CMTimeMake(3600, 600))]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError *error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; + + +} + + +#pragma mark 骚操作 +- (IBAction)magicEdit:(id)sender { + + [_videoBox clean]; + NSString *filePath = [self buildFilePath]; + __weak typeof(self) wself = self; + + // 放入原视频,换成1号的音,再把3号视频放入混音,剪其中8秒 + // 拼1号视频,给1号水印,剪其中8秒 + // 拼2号视频,给2号变速 + // 拼3号视频,旋转180,剪其中8秒 + // 把最后的视频再做一个变速 + [_videoBox appendVideoByPath:_videoPath]; + [_videoBox replaceSoundBySoundPath:_testOnePath]; + [_videoBox dubbedSoundBySoundPath:_testThreePath]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeMake(3600, 600))]; + + [_videoBox appendVideoByPath:_testOnePath]; + [_videoBox appendWaterMark:[UIImage imageNamed:@"waterMark"] relativeRect:CGRectMake(0.7, 0.7, 0.2, 0.12)]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(CMTimeMake(3600, 600), CMTimeMake(3600, 600))]; + + [_videoBox appendVideoByPath:_testTwoPath]; + [_videoBox gearBoxWithScale:2]; + + [_videoBox appendVideoByPath:_testThreePath]; + [_videoBox rotateVideoByDegress:180]; + [_videoBox rangeVideoByTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeMake(3600, 600))]; + + [_videoBox commit]; + [_videoBox gearBoxWithScale:2]; + + [_videoBox asyncFinishEditByFilePath:filePath complete:^(NSError * error) { + if (!error) { + [wself goToPlayVideoByFilePath:filePath]; + } + }]; + + +} + +- (IBAction)natureVideo:(id)sender { + [self goToPlayVideoByFilePath:_videoPath]; +} + +- (IBAction)playTest1:(id)sender { + [self goToPlayVideoByFilePath:_testOnePath]; +} + +- (IBAction)playTest2:(id)sender { + [self goToPlayVideoByFilePath:_testTwoPath]; +} + +- (IBAction)playTest3:(id)sender { + [self goToPlayVideoByFilePath:_testThreePath]; +} + + + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSECommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSECommand.h new file mode 100644 index 0000000..611a1dc --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSECommand.h @@ -0,0 +1,58 @@ +// +// WAAVSECommand.h +// WA +// +// Created by 黄锐灏 on 2017/8/14. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import +#import "WAAVSEComposition.h" +#import + + +@interface WAAVSECommand : NSObject + +- (instancetype)initWithComposition:(WAAVSEComposition *)composition; + + +@property (nonatomic , strong) WAAVSEComposition *composition; + +/** + 视频信息初始化 + + @param asset asset + */ +- (void)performWithAsset:(AVAsset *)asset; + +/** + 视频融合器初始化 + */ +- (void)performVideoCompopsition; + +/** + 音频融合器初始化 + */ +- (void)performAudioCompopsition; + + + /** + 计算旋转角度 + + @param transform transForm + @return 角度 + */ +- (NSUInteger)degressFromTransform:(CGAffineTransform)transForm; + +/** + 画布旋转 + + @param asset asset + @param degress 角度 + */ +- (void)performWithAsset:(AVAsset *)asset degress:(NSUInteger)degress; + +@end + +extern NSString* const WAAVSEExportCommandCompletionNotification; +extern NSString* const WAAVSEExportCommandError; diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSECommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSECommand.m new file mode 100644 index 0000000..db4a5e3 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSECommand.m @@ -0,0 +1,214 @@ +// +// WAAVSECommand.m +// WA +// +// Created by 黄锐灏 on 2017/8/14. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSECommand () + +@property (nonatomic , strong) AVAssetTrack *assetVideoTrack; + +@property (nonatomic , strong) AVAssetTrack *assetAudioTrack; + +@property (nonatomic , assign) NSInteger trackDegress; + +@end + +@implementation WAAVSECommand + +- (instancetype)init{ + return [self initWithComposition:[WAAVSEComposition new]]; +} + +- (instancetype)initWithComposition:(WAAVSEComposition *)composition{ + self = [super init]; + if(self != nil) { + self.composition = composition; + } + return self; +} + + +- (void)performWithAsset:(AVAsset *)asset +{ + + // 1.1、视频资源的轨道 + if (!self.assetVideoTrack) { + if ([asset tracksWithMediaType:AVMediaTypeVideo].count != 0) { + self.assetVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + } + } + + // 1.2、音频资源的轨道 + if (!self.assetAudioTrack) { + if ([asset tracksWithMediaType:AVMediaTypeAudio].count != 0) { + self.assetAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject]; + } + } + + // 2、创建混合器 + + if(!self.composition.mutableComposition) { + + // 要混合的时间 + CMTime insertionPoint = kCMTimeZero; + NSError *error = nil; + + self.composition.mutableComposition = [AVMutableComposition composition]; + // 2.1、把视频轨道加入到混合器做出新的轨道 + if (self.assetVideoTrack != nil) { + + AVMutableCompositionTrack *compostionVideoTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + + [compostionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:self.assetVideoTrack atTime:insertionPoint error:&error]; + + self.composition.duration = self.composition.mutableComposition.duration; + + self.trackDegress = [self degressFromTransform:self.assetVideoTrack.preferredTransform]; + + if (self.trackDegress % 360) { + [self performVideoCompopsition]; + }else{ + self.composition.mutableComposition.naturalSize = compostionVideoTrack.naturalSize; + } + + } + + // 2.2、把音频轨道加入到混合器做出新的轨道 + if (self.assetAudioTrack != nil) { + + AVMutableCompositionTrack *compositionAudioTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + + [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:self.assetAudioTrack atTime:insertionPoint error:&error]; + } + + } + +} + +- (void)performVideoCompopsition{ + + if(!self.composition.mutableVideoComposition) { + + self.composition.mutableVideoComposition = [AVMutableVideoComposition videoComposition]; + self.composition.mutableVideoComposition.frameDuration = CMTimeMake(1, 30); // 30 fps + self.composition.mutableVideoComposition.renderSize = self.assetVideoTrack.naturalSize; + + + AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + passThroughInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [self.composition.mutableComposition duration]); + + AVAssetTrack *videoTrack = [self.composition.mutableComposition tracksWithMediaType:AVMediaTypeVideo][0]; + + AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack]; + [passThroughLayer setTransform:self.assetVideoTrack.preferredTransform atTime:kCMTimeZero]; + passThroughInstruction.layerInstructions = @[passThroughLayer]; + + [self.composition.instructions addObject:passThroughInstruction]; + self.composition.mutableVideoComposition.instructions = self.composition.instructions; + + if (self.trackDegress == 90 || self.trackDegress == 270) { + self.composition.mutableVideoComposition.renderSize = CGSizeMake(self.assetVideoTrack.naturalSize.height, self.assetVideoTrack.naturalSize.width); + } + + self.composition.lastInstructionSize = self.composition.mutableComposition.naturalSize = self.composition.mutableVideoComposition.renderSize; + + } + +} + +- (void)performAudioCompopsition{ + if (!self.composition.mutableAudioMix) { + + self.composition.mutableAudioMix = [AVMutableAudioMix audioMix]; + + for (AVMutableCompositionTrack *compostionVideoTrack in [self.composition.mutableComposition tracksWithMediaType:AVMediaTypeAudio]) { + + AVMutableAudioMixInputParameters *audioParam = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:compostionVideoTrack]; + [audioParam setVolume:1.0 atTime:kCMTimeZero]; + [self.composition.audioMixParams addObject:audioParam]; + } + self.composition.mutableAudioMix.inputParameters = self.composition.audioMixParams; + } +} + + +- (void)performWithAsset:(AVAsset *)asset degress:(NSUInteger)degress{ + + [self performVideoCompopsition]; + + AVMutableVideoCompositionInstruction *instruction = nil; + AVMutableVideoCompositionLayerInstruction *layerInstruction = nil; + CGAffineTransform t1; + CGAffineTransform t2; + CGSize renderSize; + + // 角度调整 + degress -= degress % 360 % 90; + + if (degress == 90) { + t1 = CGAffineTransformMakeTranslation(self.composition.mutableVideoComposition.renderSize.height, 0.0); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.height, self.composition.mutableVideoComposition.renderSize.width); + }else if (degress == 180){ + t1 = CGAffineTransformMakeTranslation(self.composition.mutableVideoComposition.renderSize.width, self.composition.mutableVideoComposition.renderSize.height); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.width, self.composition.mutableVideoComposition.renderSize.height); + }else if (degress == 270){ + t1 = CGAffineTransformMakeTranslation(0.0, self.composition.mutableVideoComposition.renderSize.width); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.height, self.composition.mutableVideoComposition.renderSize.width); + }else{ + t1 = CGAffineTransformMakeTranslation(0.0, 0.0); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.width, self.composition.mutableVideoComposition.renderSize.height); + } + + // Rotate transformation + t2 = CGAffineTransformRotate(t1, (degress / 180.0) * M_PI ); + + self.composition.mutableComposition.naturalSize = self.composition.mutableVideoComposition.renderSize = renderSize; + + instruction = (AVMutableVideoCompositionInstruction *)(self.composition.mutableVideoComposition.instructions)[0]; + layerInstruction = (AVMutableVideoCompositionLayerInstruction *)(instruction.layerInstructions)[0]; + + CGAffineTransform existingTransform; + + if (![layerInstruction getTransformRampForTime:[self.composition.mutableComposition duration] startTransform:&existingTransform endTransform:NULL timeRange:NULL]) { + [layerInstruction setTransform:t2 atTime:kCMTimeZero]; + } else { + CGAffineTransform newTransform = CGAffineTransformConcat(existingTransform, t2); + [layerInstruction setTransform:newTransform atTime:kCMTimeZero]; + } + + instruction.layerInstructions = @[layerInstruction]; + self.composition.mutableVideoComposition.instructions = @[instruction]; + +} + +- (NSUInteger)degressFromTransform:(CGAffineTransform)transForm +{ + NSUInteger degress = 0; + + if(transForm.a == 0 && transForm.b == 1.0 && transForm.c == -1.0 && transForm.d == 0){ + // Portrait + degress = 90; + }else if(transForm.a == 0 && transForm.b == -1.0 && transForm.c == 1.0 && transForm.d == 0){ + // PortraitUpsideDown + degress = 270; + }else if(transForm.a == 1.0 && transForm.b == 0 && transForm.c == 0 && transForm.d == 1.0){ + // LandscapeRight + degress = 0; + }else if(transForm.a == -1.0 && transForm.b == 0 && transForm.c == 0 && transForm.d == -1.0){ + // LandscapeLeft + degress = 180; + } + + return degress; +} + + +NSString *const WAAVSEExportCommandCompletionNotification = @"WAAVSEExportCommandCompletionNotification"; +NSString* const WAAVSEExportCommandError = @"WAAVSEExportCommandError"; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEComposition.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEComposition.h new file mode 100644 index 0000000..98fb424 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEComposition.h @@ -0,0 +1,71 @@ +// +// WAAVSEComposition.h +// YCH +// +// Created by 黄锐灏 on 2017/9/26. +// Copyright © 2017 黄锐灏. All rights reserved. +// + +#import +#import +NS_ASSUME_NONNULL_BEGIN + +@interface WAAVSEComposition : NSObject + +/** + 视频轨道信息 + */ +@property (nonatomic , strong) AVMutableComposition *mutableComposition; + +/** + 视频操作指令 + */ +@property (nonatomic , strong) AVMutableVideoComposition *mutableVideoComposition; + +/** + 音频操作指令 + */ +@property (nonatomic , strong) AVMutableAudioMix *mutableAudioMix; + +/** + 视频时长(变速/裁剪后) PS:后续版本会为每条轨道单独设置duration + */ +@property (nonatomic , assign) CMTime duration; + +/** + 视频分辩率 + */ +@property (nonatomic , copy) NSString *presetName; + +/** + 视频质量 + */ +@property (nonatomic , assign) NSInteger videoQuality; + +/** + 视频操作参数数组 + */ +@property (nonatomic , strong) NSMutableArray *instructions; + +/** + 音频操作参数数组 + */ +@property (nonatomic , strong) NSMutableArray *audioMixParams; + +/** + 画布父容器 + */ +@property (nonatomic , strong) CALayer *parentLayer; + +/** + 原视频容器 + */ +@property (nonatomic , strong) CALayer *videoLayer; + + +@property (nonatomic , assign) CGSize lastInstructionSize; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEComposition.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEComposition.m new file mode 100644 index 0000000..e367451 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEComposition.m @@ -0,0 +1,27 @@ +// +// WAAVSEComposition.m +// YCH +// +// Created by 黄锐灏 on 2017/9/26. +// Copyright © 2017 黄锐灏. All rights reserved. +// + +#import "WAAVSEComposition.h" + +@implementation WAAVSEComposition + +- (NSMutableArray *)audioMixParam{ + if (!_audioMixParams) { + _audioMixParams = [NSMutableArray array]; + } + return _audioMixParams; +} + +- (NSMutableArray *)instructions{ + if (!_instructions) { + _instructions = [NSMutableArray array]; + } + return _instructions; +} + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEDubbedCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEDubbedCommand.h new file mode 100644 index 0000000..025990e --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEDubbedCommand.h @@ -0,0 +1,30 @@ +// +// WAAVSEDubbedCommand.h +// WA +// +// Created by 黄锐灏 on 2017/11/27. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSEDubbedCommand : WAAVSECommand + +- (void)performWithAsset:(AVAsset *)asset mixAsset:(AVAsset *)mixAsset; + +/** + 插入时间 + */ +@property (nonatomic , assign) CMTime insertTime; + +/** + 原音频音量 0.0~1.0 + */ +@property (nonatomic , assign) float audioVolume; + +/** + 配音音量 0.0~1.0 + */ +@property (nonatomic , assign) float mixVolume; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEDubbedCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEDubbedCommand.m new file mode 100644 index 0000000..57b9f8c --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEDubbedCommand.m @@ -0,0 +1,69 @@ +// +// WAAVSEDubbedCommand.m +// WA +// +// Created by 黄锐灏 on 2017/11/27. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSEDubbedCommand.h" + +@implementation WAAVSEDubbedCommand + +- (instancetype)init +{ + if (self = [super init]) { + self.audioVolume = 0.5; + self.mixVolume = 0.5; + self.insertTime = kCMTimeZero; + } + return self; +} + +- (instancetype)initWithComposition:(WAAVSEComposition *)composition{ + if (self = [super initWithComposition:composition]) { + self.audioVolume = 0.5; + self.mixVolume = 0.5; + self.insertTime = kCMTimeZero; + } + return self; +} + +- (void)performWithAsset:(AVAsset *)asset mixAsset:(AVAsset *)mixAsset{ + + [super performWithAsset:asset]; + + [super performAudioCompopsition]; + + if (CMTimeCompare(self.composition.duration, _insertTime) != 1) { + return; + } + + for (AVMutableAudioMixInputParameters *parameters in self.composition.audioMixParams) { + [parameters setVolume:self.audioVolume atTime:kCMTimeZero]; + } + + AVAssetTrack *audioTrack = NULL; + if ([mixAsset tracksWithMediaType:AVMediaTypeAudio].count != 0) { + audioTrack = [[mixAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; + } + + AVMutableCompositionTrack *mixAudioTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + + + CMTime endPoint = CMTimeAdd(_insertTime, mixAsset.duration); + CMTime duration = CMTimeSubtract(CMTimeMinimum(endPoint, self.composition.duration), _insertTime); + + [mixAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:audioTrack atTime:_insertTime error:nil]; + + AVMutableAudioMixInputParameters *mixParam = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mixAudioTrack]; + [mixParam setVolume:self.mixVolume atTime:_insertTime]; + [self.composition.audioMixParams addObject:mixParam]; + + self.composition.mutableAudioMix.inputParameters = self.composition.audioMixParams; + +} + + + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEExportCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEExportCommand.h new file mode 100644 index 0000000..03eefa6 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEExportCommand.h @@ -0,0 +1,22 @@ +// +// WAAVSEExportCommand.h +// WA +// +// Created by 黄锐灏 on 2017/8/14. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" +#import + +@interface WAAVSEExportCommand : WAAVSECommand + +@property AVAssetExportSession *exportSession; + +@property (nonatomic , assign) NSInteger videoQuality; + +- (void)performSaveByPath:(NSString *)path; + +- (void)performSaveAsset:(AVAsset *)asset byPath:(NSString *)path; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEExportCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEExportCommand.m new file mode 100644 index 0000000..df446c0 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEExportCommand.m @@ -0,0 +1,97 @@ +// +// WAAVSEExportCommand.m +// WA +// +// Created by 黄锐灏 on 2017/8/14. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSEExportCommand.h" +@interface WAAVSEExportCommand () + +@property (nonatomic , assign) CGFloat ratioParam; + +@end + +@implementation WAAVSEExportCommand + +- (instancetype)initWithComposition:(WAAVSEComposition *)composition{ + if (self = [super initWithComposition:composition]) { + self.videoQuality = 6; + } + return self; +} + +- (void)dealloc{ + +} + +- (void)performSaveAsset:(AVAsset *)asset byPath:(NSString *)path{ + + NSFileManager *manager = [NSFileManager defaultManager]; + [manager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; + // Remove Existing File + [manager removeItemAtPath:path error:nil]; + + // Step 2 + if (self.composition.presetName.length == 0) { + self.composition.presetName = AVAssetExportPresetHighestQuality; + } + + self.exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:self.composition.presetName]; + self.exportSession.shouldOptimizeForNetworkUse = YES; + self.exportSession.videoComposition = self.composition.mutableVideoComposition; + self.exportSession.audioMix = self.composition.mutableAudioMix; + + + if (self.videoQuality) { + + if ([self.composition.presetName isEqualToString:AVAssetExportPreset640x480]) { + self.ratioParam = 0.02; + } + + if ([self.composition.presetName isEqualToString:AVAssetExportPreset960x540]) { + self.ratioParam = 0.04; + } + if ([self.composition.presetName isEqualToString:AVAssetExportPreset1280x720]) { + self.ratioParam = 0.08; + } + + if (self.ratioParam) { + self.exportSession.fileLengthLimit = CMTimeGetSeconds(self.composition.duration) * self.ratioParam * self.composition.videoQuality * 1024 * 1024; + } + + } + + + self.exportSession.outputURL = [NSURL fileURLWithPath:path]; + self.exportSession.outputFileType = AVFileTypeMPEG4; + [self.exportSession exportAsynchronouslyWithCompletionHandler:^(void){ + + switch (self.exportSession.status) { + case AVAssetExportSessionStatusCompleted: + [[NSNotificationCenter defaultCenter] postNotificationName:WAAVSEExportCommandCompletionNotification object:self]; + break; + case AVAssetExportSessionStatusFailed: + NSLog(@"%@",self.exportSession.error); + [[NSNotificationCenter defaultCenter] + postNotificationName:WAAVSEExportCommandCompletionNotification + object:self userInfo:@{WAAVSEExportCommandError:self.exportSession.error}]; + break; + case AVAssetExportSessionStatusCancelled: + [[NSNotificationCenter defaultCenter] + postNotificationName:WAAVSEExportCommandCompletionNotification + object:self userInfo:@{WAAVSEExportCommandError:[NSError errorWithDomain:AVFoundationErrorDomain code:-10000 userInfo:@{NSLocalizedFailureReasonErrorKey:@"User cancel process!"}]}]; + break; + default: + break; + } + + }]; +} + +- (void)performSaveByPath:(NSString *)path{ + [self performSaveAsset:self.composition.mutableComposition byPath:path]; +} + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommand.h new file mode 100644 index 0000000..6cae0a3 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommand.h @@ -0,0 +1,19 @@ +// +// WAAVSEGearboxCommand.h +// WA +// +// Created by 黄锐灏 on 2018/1/5. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" +@class WAAVSEGearboxCommandModel; + +@interface WAAVSEGearboxCommand : WAAVSECommand + +- (void)performWithAsset:(AVAsset *)asset scale:(CGFloat)scale; + +- (void)performWithAsset:(AVAsset *)asset models:(NSArray *)gearboxModels; + +@end + diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommand.m new file mode 100644 index 0000000..60fa770 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommand.m @@ -0,0 +1,84 @@ +// +// WAAVSEGearboxCommand.m +// WA +// +// Created by 黄锐灏 on 2018/1/5. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSEGearboxCommand.h" +#import "WAAVSEGearboxCommandModel.h" + +@interface WAAVSEGearboxCommand () + +@end + +@implementation WAAVSEGearboxCommand + +- (void)performWithAsset:(AVAsset *)asset scale:(CGFloat)scale{ + [super performWithAsset:asset]; + + CMTime insertPoint = kCMTimeZero; + for (AVMutableVideoCompositionInstruction *instruction in self.composition.instructions) { + CMTime duration = instruction.timeRange.duration; + [instruction setTimeRange:CMTimeRangeMake(insertPoint, CMTimeMake(duration.value / scale, duration.timescale))]; + insertPoint = CMTimeAdd(instruction.timeRange.start, instruction.timeRange.duration); + } + + + [[self.composition.mutableComposition tracksWithMediaType:AVMediaTypeVideo] enumerateObjectsUsingBlock:^(AVMutableCompositionTrack *videoTrack, NSUInteger idx, BOOL * _Nonnull stop) { + // AVMutableVideoCompositionInstruction *instruction = self.composition.instructions[1]; + [videoTrack scaleTimeRange:videoTrack.timeRange toDuration: CMTimeMake(videoTrack.timeRange.duration.value / scale, videoTrack.timeRange.duration.timescale)]; + }]; + + [[self.composition.mutableComposition tracksWithMediaType:AVMediaTypeAudio] enumerateObjectsUsingBlock:^(AVMutableCompositionTrack *audioTrack, NSUInteger idx, BOOL * _Nonnull stop) { + [audioTrack scaleTimeRange:audioTrack.timeRange toDuration: CMTimeMake(audioTrack.timeRange.duration.value / scale, audioTrack.timeRange.duration.timescale)]; + }]; + + self.composition.duration = CMTimeMultiplyByFloat64(self.composition.duration, 1 / scale); + + // 保证最后一条能到视频最后 + AVMutableVideoCompositionInstruction *instruction = [self.composition.instructions lastObject]; + [instruction setTimeRange:CMTimeRangeMake(instruction.timeRange.start, CMTimeSubtract(self.composition.duration, instruction.timeRange.start))]; +} + + +- (void)performWithAsset:(AVAsset *)asset models:(NSArray *)gearboxModels{ + [super performWithAsset:asset]; + + if (self.composition.instructions.count > 1) { + NSAssert(NO, @"This method does not support multi-video processing for the time being."); + } + + CMTime scaleDuration = kCMTimeZero; + CMTime duration = kCMTimeZero; + + for (WAAVSEGearboxCommandModel *model in gearboxModels) { + + scaleDuration = CMTimeMultiplyByFloat64(model.duration, 1 / model.scale); + // 视图变速 + for (AVMutableCompositionTrack *videoTrack in [self.composition.mutableComposition tracksWithMediaType:AVMediaTypeVideo]) { + [videoTrack scaleTimeRange:CMTimeRangeMake(model.beganDuration, model.duration) toDuration:scaleDuration]; + } + + // 音频变速 + for (AVMutableCompositionTrack *audioTrack in [self.composition.mutableComposition tracksWithMediaType:AVMediaTypeAudio]) { + + [audioTrack scaleTimeRange:CMTimeRangeMake(model.beganDuration, model.duration) toDuration: scaleDuration]; + } + + // instruction变速 + duration = CMTimeAdd(duration, model.duration); + + } + + + for (AVMutableVideoCompositionInstruction *instruction in self.composition.instructions) { + [instruction setTimeRange:CMTimeRangeMake(kCMTimeZero,self.composition.duration)]; + } + +} + +@end + + diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommandModel.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommandModel.h new file mode 100644 index 0000000..7d59d7a --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommandModel.h @@ -0,0 +1,20 @@ +// +// WAAVSEGearboxCommandModel.h +// WA +// +// Created by 黄锐灏 on 2018/1/5. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import +#import +@interface WAAVSEGearboxCommandModel : NSObject + +@property (nonatomic , assign) CMTime beganDuration; + +@property (nonatomic , assign) CMTime duration; + +@property (nonatomic , assign) CGFloat scale; + + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommandModel.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommandModel.m new file mode 100644 index 0000000..84140a1 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEGearboxCommandModel.m @@ -0,0 +1,13 @@ +// +// WAAVSEGearboxCommandModel.m +// WA +// +// Created by 黄锐灏 on 2018/1/5. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSEGearboxCommandModel.h" + +@implementation WAAVSEGearboxCommandModel + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEImageMixCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEImageMixCommand.h new file mode 100644 index 0000000..f49b8c2 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEImageMixCommand.h @@ -0,0 +1,20 @@ +// +// WAAVSEImageMixCommand.h +// WA +// +// Created by 黄锐灏 on 2017/9/25. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSEImageMixCommand : WAAVSECommand + +@property (nonatomic , assign) BOOL imageBg; + +@property (nonatomic , strong) UIImage *image; + +// 传回要放的图片位置 +- (void)imageLayerRectWithVideoSize:(CGRect (^) (CGSize videoSize))imageLayerRect; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEImageMixCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEImageMixCommand.m new file mode 100644 index 0000000..8aba137 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEImageMixCommand.m @@ -0,0 +1,77 @@ +// +// WAAVSEImageMixCommand.m +// WA +// +// Created by 黄锐灏 on 2017/9/25. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSEImageMixCommand.h" + + +@interface WAAVSEImageMixCommand () + +@property (nonatomic , copy) CGRect (^imageLayerRect)(CGSize); + +@end + +@implementation WAAVSEImageMixCommand + +- (void)performWithAsset:(AVAsset *)asset{ + + [super performWithAsset:asset]; + CGSize videoSize; + + // 3、通过videoCompostion合成 + if ([[self.composition.mutableComposition tracksWithMediaType:AVMediaTypeVideo] count] != 0) { + // 3.1、 + + // 3.2、创建视频画面合成器 + [super performVideoCompopsition]; + + videoSize = self.composition.mutableVideoComposition.renderSize; + + CALayer *imageLayer; + if (self.imageLayerRect) { + imageLayer = [self buildImageLayerWithRect:self.imageLayerRect(videoSize)]; + } + + if (!self.composition.videoLayer || !self.composition.parentLayer) { + CALayer *parentLayer = [CALayer layer]; + CALayer *videoLayer = [CALayer layer]; + parentLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height); + videoLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height); + self.composition.videoLayer = videoLayer; + self.composition.parentLayer = parentLayer; + } + + if (self.imageBg) { + self.composition.videoLayer.opaque = YES; + self.composition.videoLayer.opacity = 0.8; + [self.composition.parentLayer addSublayer:imageLayer]; + [self.composition.parentLayer addSublayer:self.composition.videoLayer]; + }else{ + [self.composition.parentLayer addSublayer:self.composition.videoLayer]; + [self.composition.parentLayer addSublayer:imageLayer]; + } + + self.composition.mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer: self.composition.videoLayer inLayer: self.composition.parentLayer]; + + } + +} + +- (void)imageLayerRectWithVideoSize:(CGRect (^)(CGSize))imageLayerRect{ + if (imageLayerRect) { + self.imageLayerRect = imageLayerRect; + } +} + +- (CALayer *)buildImageLayerWithRect:(CGRect)rect{ + + CALayer *imageLayer = [CALayer layer]; + imageLayer.contents = (__bridge id) (self.image.CGImage); + imageLayer.frame = rect; + return imageLayer; +} +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERangeCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERangeCommand.h new file mode 100644 index 0000000..79c2c35 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERangeCommand.h @@ -0,0 +1,15 @@ +// +// WAAVSERangeCommand.h +// WA +// +// Created by 黄锐灏 on 2018/1/29. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSERangeCommand : WAAVSECommand + +- (void)performWithAsset:(AVAsset *)asset timeRange:(CMTimeRange)range; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERangeCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERangeCommand.m new file mode 100644 index 0000000..a1a741c --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERangeCommand.m @@ -0,0 +1,52 @@ +// +// WAAVSERangeCommand.m +// WA +// +// Created by 黄锐灏 on 2018/1/29. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSERangeCommand.h" + +@implementation WAAVSERangeCommand + +- (void)performWithAsset:(AVAsset *)asset timeRange:(CMTimeRange)range{ + + [super performWithAsset:asset]; + + if (CMTimeCompare(self.composition.duration, CMTimeAdd(range.start, range.duration)) != 1) { +// NSAssert(NO, @"Range out of video duration"); + } + + // 轨道裁剪 + for (AVMutableCompositionTrack *compositionTrack in [self.composition.mutableComposition tracksWithMediaType:AVMediaTypeAudio]) { + + [self subTimeRaneWithTrack:compositionTrack range:range]; + + } + + for (AVMutableCompositionTrack *compositionTrack in [self.composition.mutableComposition tracksWithMediaType:AVMediaTypeVideo]) { + + [self subTimeRaneWithTrack:compositionTrack range:range]; + + } + + self.composition.duration = range.duration; + +} + +- (void)subTimeRaneWithTrack:(AVMutableCompositionTrack *)compositionTrack range:(CMTimeRange)range{ + + CMTime endPoint = CMTimeAdd(range.start, range.duration); + if (CMTimeCompare(self.composition.duration,endPoint) != -1) { + [compositionTrack removeTimeRange:CMTimeRangeMake(endPoint,CMTimeSubtract(self.composition.duration, endPoint))]; + } + + if (CMTimeGetSeconds(range.start)) { + [compositionTrack removeTimeRange:CMTimeRangeMake(kCMTimeZero, range.start)]; + } + + +} + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEReplaceSoundCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEReplaceSoundCommand.h new file mode 100644 index 0000000..ba4e1a1 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEReplaceSoundCommand.h @@ -0,0 +1,15 @@ +// +// WAAVSEReplaceSoundCommand.h +// WA +// +// Created by 黄锐灏 on 2017/11/23. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSEReplaceSoundCommand : WAAVSECommand + +- (void)performWithAsset:(AVAsset *)asset replaceAsset:(AVAsset *)replaceAsset; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEReplaceSoundCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEReplaceSoundCommand.m new file mode 100644 index 0000000..3b21f73 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEReplaceSoundCommand.m @@ -0,0 +1,36 @@ +// +// WAAVSEReplaceSoundCommand.m +// WA +// +// Created by 黄锐灏 on 2017/11/23. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSEReplaceSoundCommand.h" + +@implementation WAAVSEReplaceSoundCommand + +- (void)performWithAsset:(AVAsset *)asset replaceAsset:(AVAsset *)replaceAsset{ + + [super performWithAsset:asset]; + + + CMTime insertionPoint = kCMTimeZero; + CMTime duration; + NSError *error = nil; + + NSArray *natureTrackAry = [[self.composition.mutableComposition tracksWithMediaType:AVMediaTypeAudio] copy]; + + for (AVCompositionTrack *track in natureTrackAry) { + [self.composition.mutableComposition removeTrack:track]; + } + + duration = CMTimeMinimum([replaceAsset duration], self.composition.duration); + + for (AVAssetTrack *audioTrack in [replaceAsset tracksWithMediaType:AVMediaTypeAudio]) { + AVMutableCompositionTrack *compositionAudioTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:audioTrack atTime:insertionPoint error:&error]; + } + +} +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERotateCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERotateCommand.h new file mode 100644 index 0000000..27710fe --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERotateCommand.h @@ -0,0 +1,15 @@ +// +// WAAVSERotateCommand.h +// WA +// +// Created by 黄锐灏 on 2018/1/29. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSERotateCommand : WAAVSECommand + +- (void)performWithAsset:(AVAsset *)asset degress:(NSUInteger)degress; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERotateCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERotateCommand.m new file mode 100644 index 0000000..6c7bcc5 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSERotateCommand.m @@ -0,0 +1,124 @@ +// +// WAAVSERotateCommand.m +// WA +// +// Created by 黄锐灏 on 2018/1/29. +// Copyright © 2018年 黄锐灏. All rights reserved. +// + +#import "WAAVSERotateCommand.h" + +@implementation WAAVSERotateCommand + +- (void)performWithAsset:(AVAsset *)asset degress:(NSUInteger)degress{ + [super performWithAsset:asset]; + + [super performVideoCompopsition]; + + if(self.composition.mutableVideoComposition.instructions.count > 1){ + NSAssert(NO, @"This method does not support multi-video processing for the time being."); + //暂不支持不同分辩率的视频合并马上再旋转 + //ps:可以分开操作,先旋转一个再apeed再旋转即可,使用骚操作完成 + } + + degress -= degress % 360 % 90 ; + + for (AVMutableVideoCompositionInstruction *instruction in self.composition.mutableVideoComposition.instructions) { + + AVMutableVideoCompositionLayerInstruction *layerInstruction = (AVMutableVideoCompositionLayerInstruction *)(instruction.layerInstructions)[0]; + CGAffineTransform t1; + CGAffineTransform t2; + CGSize renderSize; + + // 角度调整 + degress -= degress % 360 % 90; + + if (degress == 90) { + t1 = CGAffineTransformMakeTranslation(self.composition.mutableVideoComposition.renderSize.height, 0.0); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.height, self.composition.mutableVideoComposition.renderSize.width); + }else if (degress == 180){ + t1 = CGAffineTransformMakeTranslation(self.composition.mutableVideoComposition.renderSize.width, self.composition.mutableVideoComposition.renderSize.height); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.width, self.composition.mutableVideoComposition.renderSize.height); + }else if (degress == 270){ + t1 = CGAffineTransformMakeTranslation(0.0, self.composition.mutableVideoComposition.renderSize.width); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.height, self.composition.mutableVideoComposition.renderSize.width); + }else{ + t1 = CGAffineTransformMakeTranslation(0.0, 0.0); + renderSize = CGSizeMake(self.composition.mutableVideoComposition.renderSize.width, self.composition.mutableVideoComposition.renderSize.height); + } + + // Rotate transformation + t2 = CGAffineTransformRotate(t1, (degress / 180.0) * M_PI ); + + self.composition.mutableComposition.naturalSize = self.composition.mutableVideoComposition.renderSize = renderSize; + + CGAffineTransform existingTransform; + + if (![layerInstruction getTransformRampForTime:[self.composition.mutableComposition duration] startTransform:&existingTransform endTransform:NULL timeRange:NULL]) { + [layerInstruction setTransform:t2 atTime:kCMTimeZero]; + } else { + CGAffineTransform newTransform = CGAffineTransformConcat(existingTransform, t2); + [layerInstruction setTransform:newTransform atTime:kCMTimeZero]; + } + + instruction.layerInstructions = @[layerInstruction]; + + } + + // 将容器大小旋转,若没有anmaitionTool的修改,则直接跳过此步 + if (self.composition.videoLayer || self.composition.parentLayer) { + + for (CALayer *sublayer in self.composition.parentLayer.sublayers) { + if (sublayer == self.composition.videoLayer) { + continue; + } + [self converRect:sublayer naturalRenderSize:self.composition.mutableVideoComposition.renderSize renderSize:self.composition.mutableVideoComposition.renderSize]; + sublayer.transform = CATransform3DRotate(sublayer.transform, -(degress / 180.0 * M_PI), 0, 0, 1); + } + + if (degress == 90 || degress == 270) { + self.composition.videoLayer.frame = CGRectMake(0, 0, self.composition.videoLayer.bounds.size.height, self.composition.videoLayer.bounds.size.width); + self.composition.parentLayer.frame = CGRectMake(0, 0, self.composition.parentLayer.bounds.size.height, self.composition.parentLayer.bounds.size.width); + } + + } + +} + + +// 调整旋转 +- (NSUInteger)degressFromTransform:(CGAffineTransform)transForm +{ + NSUInteger degress = 0; + + if(transForm.a == 0 && transForm.b == 1.0 && transForm.c == -1.0 && transForm.d == 0){ + // Portrait + degress = 90; + }else if(transForm.a == 0 && transForm.b == -1.0 && transForm.c == 1.0 && transForm.d == 0){ + // PortraitUpsideDown + degress = 270; + }else if(transForm.a == 1.0 && transForm.b == 0 && transForm.c == 0 && transForm.d == 1.0){ + // LandscapeRight + degress = 0; + }else if(transForm.a == -1.0 && transForm.b == 0 && transForm.c == 0 && transForm.d == -1.0){ + // LandscapeLeft + degress = 180; + } + + return degress; +} + +- (void)converRect:(CALayer *)layer naturalRenderSize:(CGSize)size renderSize:(CGSize)renderSize{ + + + + if (!CGSizeEqualToSize(size, renderSize)) { + // 还原绝对位置 + CGRect relativeRect = CGRectMake(layer.frame.origin.x / size.width, 1 - (layer.frame.origin.y + layer.bounds.size.height) / size.height, layer.bounds.size.width / size.width, layer.bounds.size.height / size.height); + + layer.frame = CGRectMake(renderSize.width * relativeRect.origin.x,renderSize.height * (1 - relativeRect.origin.y) - renderSize.height * relativeRect.size.height,renderSize.width * relativeRect.size.width, renderSize.height * relativeRect.size.height); + + } +} + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEVideoMixCommand.h b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEVideoMixCommand.h new file mode 100644 index 0000000..22e6ca5 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEVideoMixCommand.h @@ -0,0 +1,17 @@ +// +// WAAVSEVideoMixCommand.h +// WA +// +// Created by 黄锐灏 on 2017/9/15. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSECommand.h" + +@interface WAAVSEVideoMixCommand : WAAVSECommand + +- (void)performWithAsset:(AVAsset *)asset mixAsset:(AVAsset *)mixAsset; + +- (void)performWithAssets:(NSArray *)assets; + +@end diff --git a/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEVideoMixCommand.m b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEVideoMixCommand.m new file mode 100644 index 0000000..ce721bb --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAAVSeCommand/WAAVSEVideoMixCommand.m @@ -0,0 +1,144 @@ +// +// WAAVSEVideoMixCommand.m +// WA +// +// Created by 黄锐灏 on 2017/9/15. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAAVSEVideoMixCommand.h" +@interface WAAVSEVideoMixCommand () + +@end + +@implementation WAAVSEVideoMixCommand + +- (void)performWithAsset:(AVAsset *)asset mixAsset:(AVAsset *)mixAsset{ + + [super performWithAsset:asset]; + + [self mixWithAsset:mixAsset]; + + +} + +- (void)performWithAssets:(NSArray *)assets{ + + AVAsset *asset = assets[0]; + [super performWithAsset:asset]; + + for (int i = 1; i < assets.count; i ++) { + [self mixWithAsset:assets[i]]; + } + +} + +- (void)mixWithAsset:(AVAsset *)mixAsset{ + + NSError *error = nil; + + AVAssetTrack *mixAssetVideoTrack = nil; + AVAssetTrack *mixAssetAudioTrack = nil; + // Check if the asset contains video and audio tracks + if ([[mixAsset tracksWithMediaType:AVMediaTypeVideo] count] != 0) { + mixAssetVideoTrack = [mixAsset tracksWithMediaType:AVMediaTypeVideo][0]; + } + + if ([[mixAsset tracksWithMediaType:AVMediaTypeAudio] count] != 0) { + mixAssetAudioTrack = [mixAsset tracksWithMediaType:AVMediaTypeAudio][0]; + } + + if (mixAssetVideoTrack) { + + CGSize natureSize = mixAssetVideoTrack.naturalSize; + NSInteger degress = [self degressFromTransform:mixAssetVideoTrack.preferredTransform]; + + AVMutableCompositionTrack *videoTrack = [[self.composition.mutableComposition tracksWithMediaType:AVMediaTypeVideo] lastObject]; + BOOL needNewInstrunction = YES; + + if (!(degress % 360) && !self.composition.instructions.count && CGSizeEqualToSize(natureSize, self.composition.mutableComposition.naturalSize) && videoTrack) { + [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, mixAsset.duration) ofTrack:mixAssetVideoTrack atTime:self.composition.duration error:&error]; + needNewInstrunction = NO; + }else if (!(degress % 360) && self.composition.instructions.count) { + CGAffineTransform transform; + AVMutableVideoCompositionInstruction *instruction = [self.composition.instructions lastObject]; + AVMutableVideoCompositionLayerInstruction *layerInstruction = (AVMutableVideoCompositionLayerInstruction *)instruction.layerInstructions[0]; + [layerInstruction getTransformRampForTime:self.composition.duration startTransform:&transform endTransform:NULL timeRange:NULL]; + + if (CGAffineTransformEqualToTransform (transform, mixAssetVideoTrack.preferredTransform) && CGSizeEqualToSize(self.composition.lastInstructionSize, natureSize)) { + + [instruction setTimeRange:CMTimeRangeMake(instruction.timeRange.start, CMTimeAdd(instruction.timeRange.duration, mixAsset.duration))]; + [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, mixAsset.duration) ofTrack:mixAssetVideoTrack atTime:self.composition.duration error:&error]; + needNewInstrunction = NO; + }else{ + needNewInstrunction = YES; + } + } + + if (needNewInstrunction) { + + [super performVideoCompopsition]; + + AVMutableCompositionTrack *newVideoTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + + [newVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, mixAsset.duration) ofTrack:mixAssetVideoTrack atTime:self.composition.duration error:&error]; + + AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + [instruction setTimeRange:CMTimeRangeMake(self.composition.duration, mixAsset.duration)]; + + AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:newVideoTrack]; + + CGSize renderSize = self.composition.mutableVideoComposition.renderSize; + + if (degress == 90 || degress == 270) { + natureSize = CGSizeMake(natureSize.height, natureSize.width); + } + + + CGFloat scale = MIN(renderSize.width / natureSize.width, renderSize.height / natureSize.height); + + self.composition.lastInstructionSize = CGSizeMake(natureSize.width * scale, natureSize.height * scale); + + // 移至中心点 + CGPoint translate = CGPointMake((renderSize.width - natureSize.width * scale ) * 0.5, (renderSize.height - natureSize.height * scale ) * 0.5); + + CGAffineTransform naturalTransform = mixAssetVideoTrack.preferredTransform; + CGAffineTransform preferredTransform = CGAffineTransformMake(naturalTransform.a * scale, naturalTransform.b * scale, naturalTransform.c * scale, naturalTransform.d * scale, naturalTransform.tx * scale + translate.x, naturalTransform.ty * scale + translate.y); + + [layerInstruction setTransform:preferredTransform atTime:kCMTimeZero]; + + instruction.layerInstructions = @[layerInstruction]; + + [self.composition.instructions addObject:instruction]; + self.composition.mutableVideoComposition.instructions = self.composition.instructions; + } + + } + + if (mixAssetAudioTrack) { + if (self.composition.mutableAudioMix) { + AVMutableCompositionTrack *audioTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, mixAsset.duration) ofTrack:mixAssetAudioTrack atTime:self.composition.duration error:&error]; + + AVMutableAudioMixInputParameters *audioParam = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mixAssetAudioTrack]; + [audioParam setVolume:1.0 atTime:kCMTimeZero]; + [self.composition.audioMixParams addObject:audioParam]; + + self.composition.mutableAudioMix.inputParameters = self.composition.audioMixParams; + }else{ + + AVMutableCompositionTrack *audioTrack = [[self.composition.mutableComposition tracksWithMediaType:AVMediaTypeAudio] lastObject]; + + if (!audioTrack) { + audioTrack = [self.composition.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + } + [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, mixAsset.duration) ofTrack:mixAssetAudioTrack atTime:self.composition.duration error:&error]; + } + + } + + self.composition.duration = CMTimeAdd(self.composition.duration, mixAsset.duration); + +} + +@end diff --git a/WAVideoBox/WAVideoBox/WAVideoBox.h b/WAVideoBox/WAVideoBox/WAVideoBox.h new file mode 100644 index 0000000..9bb6a94 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAVideoBox.h @@ -0,0 +1,154 @@ +// +// WAVideoBox.h +// WA +// +// Created by 黄锐灏 on 17/9/10. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import +#import +#import "WAAVSEGearboxCommand.h" +typedef NS_ENUM(NSUInteger,WAVideoExportRatio) { + WAVideoExportRatioLowQuality,// 自动分辩率 + WAVideoExportRatioMediumQuality,// 自动分辩率 + WAVideoExportRatioHighQuality, // 自动分辩率 + WAVideoExportRatio640x480, + WAVideoExportRatio960x540, + WAVideoExportRatio1280x720 +}; + +/* + PS:为了灵活的调用、任意使用,效果与代码的调用顺序有关 + 比如先变速,再换音轨,明显后面才换的音轨不会有变速效果 + + WAVideoBox的工作区域: + ----缓存区,appedVideo后缓存区域 + ----工作区,视频指令区域,只有在这区域的视频才是有效操作 + ----合成区,完成视频指令后待合成区域 + + 1、appendVideo:会将视频加入到缓存区,将工作区内容合成一个视频(无法再拆散),并移到合成区,清空工作区 + 2、视频操作指令:缓存区视频放到工作区,视频操作只对工作区视频有效 + 3、commit:合成区域,将缓存区,合成区的视频移到工作区,视频操作对所有视频有效 + + tip:线程安全,适用于短视频处理 + +*/ + +@interface WAVideoBox : NSObject + +#pragma mark 输出设置 +/** + 输出的视频分辨率,默认为960 * 540,需要在appendVideo前调用,若已经有视频在里面,设置无效 + */ +@property (nonatomic , assign) WAVideoExportRatio ratio; + +/** + 输出的视频质量(0~10),默认为0(自动),6差不多抖音视频质量平级,自动分辩率下无效 + */ +@property (nonatomic , assign) NSInteger videoQuality; + + +#pragma mark 资源 +/** + 拼接一个视频到缓存区 + @param videoPath 视频地址 + @return 加入状态 + */ +- (BOOL)appendVideoByPath:(NSString *)videoPath; + +/** + 拼接一个视频到缓存区 + @param videoAsset 视频资源 + @return 加入状态 + */ +- (BOOL)appendVideoByAsset:(AVAsset *)videoAsset; + +/** + 视频提交,将所有的视频提取到工作区,可以对所有视频操作,不可逆 + */ +- (void)commit; + +/** + 以下所有的视频操作会将缓存区的视频提取到工作区 + */ +#pragma mark 操作指令 +#pragma mark 裁取 +/** + 视频截取 + @param range 时间范围,超出则为无效操作 + @return 操作状态 + */ +- (BOOL)rangeVideoByTimeRange:(CMTimeRange)range; + +/** + 视频旋转 + @param degress 角度,内部会调用%90,保证90度的倍数旋转 + @return 操作状态 + */ +- (BOOL)rotateVideoByDegress:(NSInteger)degress; + +#pragma mark 水印 +/** + 为视频加入水印 + @param waterImg 图片 + @param relativeRect 相对尺寸 x ,y , width , height值均为 (0 ~ 1) + @return 操作状态 + */ +- (BOOL)appendWaterMark:(UIImage *)waterImg relativeRect:(CGRect)relativeRect; + +#pragma mark 变速 +/** + 视频按scale变速 + @param scale 速度倍率 + @return 操作状态 + */ +- (BOOL)gearBoxWithScale:(CGFloat)scale; + +/** + 按视频时间段变速 + @param scaleArray 速度倍率模型数组 + @return 操作状态 + */ +- (BOOL)gearBoxTimeByScaleArray:(NSArray *)scaleArray; + +#pragma mark 换音 +- (BOOL)replaceSoundBySoundPath:(NSString *)soundPath; + +#pragma mark 混音 +- (BOOL)dubbedSoundBySoundPath:(NSString *)soundPath; + +/** + 混音并调整原声立体声音量 + @param soundPath 声音地址 + @param volume 原声音量 + @param mixVolume 合声音量 + @param insetDuration 在哪里插入 + @return 操作状态 + */ +- (BOOL)dubbedSoundBySoundPath:(NSString *)soundPath volume:(CGFloat)volume mixVolume:(CGFloat)mixVolume insertTime:(CGFloat)insetDuration; + +#pragma mark 处理 +/** + 异步视频处理 + @param complete 回调block + */ +- (void)asyncFinishEditByFilePath:(NSString *)filePath complete:(void (^)(NSError *error))complete; + +/** + 同步视频处理 + @param complete 回调block + */ +- (void)syncFinishEditByFilePath:(NSString *)filePath complete:(void (^)(NSError *error))complete; + +/** + 取消操作 + */ +- (void)cancelEdit; + +/** + 清空所有数据 + */ +- (void)clean; + +@end diff --git a/WAVideoBox/WAVideoBox/WAVideoBox.m b/WAVideoBox/WAVideoBox/WAVideoBox.m new file mode 100644 index 0000000..00b73f3 --- /dev/null +++ b/WAVideoBox/WAVideoBox/WAVideoBox.m @@ -0,0 +1,625 @@ +// +// WAVideoTool.m +// WA +// +// Created by 黄锐灏 on 17/4/24. +// Copyright © 2017年 黄锐灏. All rights reserved. +// + +#import "WAVideoBox.h" +#import +#import +#import "WAAVSEExportCommand.h" +#import "WAAVSEVideoMixCommand.h" +#import "WAAVSEImageMixCommand.h" +#import "WAAVSEReplaceSoundCommand.h" +#import "WAAVSEGearboxCommand.h" +#import "WAAVSERangeCommand.h" +#import "WAAVSERotateCommand.h" +#import "WAAVSEDubbedCommand.h" +#import + +@interface WAVideoBox () + +@property (nonatomic , strong) WAAVSEComposition *cacheComposition; + +@property (nonatomic , strong) NSMutableArray *workSpace; + +@property (nonatomic , strong) NSMutableArray *composeSpace; + +@property (nonatomic , strong) NSMutableArray *tmpVideoSpace; + +@property (nonatomic , weak) WAAVSEExportCommand *exportCommand; + +@property (nonatomic , assign) NSInteger directCompostionIndex; + +@property (nonatomic , copy) NSString *filePath; + +@property (nonatomic , copy) NSString *tmpPath; + +@property (nonatomic , copy) void (^editorComplete)(NSError *error); + +@property (nonatomic , copy) NSString *presetName; + +@property (nonatomic , assign ,getter=isSuspend) BOOL suspend; //线程 挂起 + +@property (nonatomic , assign ,getter=isCancel) BOOL cancel; //线程 挂起 + +@end + +dispatch_queue_t _contextQueue; +static void *contextQueueKey = &contextQueueKey; + +dispatch_queue_t _processQueue; +static void *processQueueKey = &processQueueKey; + +NSString *_tmpDirectory; + + +void runSynchronouslyOnVideoProcessingQueue(void (^block)(void)) +{ + if (dispatch_get_specific(processQueueKey)){ + block(); + }else{ + dispatch_sync(_processQueue, block); + } +} + +void runAsynchronouslyOnVideoProcessingQueue(void (^block)(void)) +{ + + dispatch_async(_processQueue, block); +} + +void runSynchronouslyOnVideoContextQueue(void (^block)(void)) +{ + if (dispatch_get_specific(contextQueueKey)){ + block(); + }else{ + dispatch_sync(_contextQueue, block); + } +} + +void runAsynchronouslyOnVideoContextQueue(void (^block)(void)) +{ + + dispatch_async(_contextQueue, block); +} + +@implementation WAVideoBox + + ++ (void)initialize{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _tmpDirectory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WAVideoBoxTmp"]; + + processQueueKey = &processQueueKey; + + _contextQueue = dispatch_queue_create("VideoBoxContextQueue", DISPATCH_QUEUE_SERIAL); + dispatch_queue_set_specific(_contextQueue, contextQueueKey, &contextQueueKey, NULL); + + _processQueue = dispatch_queue_create("VideoBoxProcessQueue", DISPATCH_QUEUE_SERIAL); + dispatch_queue_set_specific(_processQueue, processQueueKey, &processQueueKey, NULL); + + if (![[NSFileManager defaultManager] fileExistsAtPath:_tmpDirectory]) { + [[NSFileManager defaultManager] createDirectoryAtPath:_tmpDirectory withIntermediateDirectories:YES attributes:nil error:NULL]; + } + }); +} + +#pragma mark life cycle +- (instancetype)init{ + self = [super init]; + + self.videoQuality = 0; + self.ratio = WAVideoExportRatio960x540; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVEditorNotification:) name:WAAVSEExportCommandCompletionNotification object:nil]; + + return self; +} + +- (void)dealloc{ + if (self.isSuspend) { + dispatch_resume(_contextQueue); + } + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark pubilc method +#pragma mark 资源 +- (BOOL)appendVideoByPath:(NSString *)videoPath{ + + if (videoPath.length == 0 ) { + return NO; + } + + AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:videoPath]]; + return [self appendVideoByAsset:asset]; + +} + +- (BOOL)appendVideoByAsset:(AVAsset *)videoAsset{ + + if (!videoAsset || !videoAsset.playable) { + return NO; + } + + runSynchronouslyOnVideoProcessingQueue(^{ // 取消指令 + self.cancel = NO; + }); + + runAsynchronouslyOnVideoContextQueue(^{ + // 清空工作区 + [self commitCompostionToComposespace]; + + if (!self.cacheComposition) { + self.cacheComposition = [WAAVSEComposition new]; + self.cacheComposition.presetName = self.presetName; + self.cacheComposition.videoQuality = self.videoQuality; + WAAVSECommand *command = [[WAAVSECommand alloc] initWithComposition:self.cacheComposition]; + [command performWithAsset:videoAsset]; + }else{ + WAAVSEVideoMixCommand *mixcommand = [[WAAVSEVideoMixCommand alloc] initWithComposition:self.cacheComposition]; + [mixcommand performWithAsset:self.cacheComposition.mutableComposition mixAsset:videoAsset]; + } + + }); + return YES; +} + +- (void)commit{ + + runAsynchronouslyOnVideoContextQueue(^{ + + [self.workSpace insertObjects:self.composeSpace atIndexes:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, self.composeSpace.count)]]; + + [self.composeSpace removeAllObjects]; + + [self commitCompostionToWorkspace]; + + }); + +} + +#pragma mark 裁剪 +- (BOOL)rangeVideoByTimeRange:(CMTimeRange)range{ + + runAsynchronouslyOnVideoContextQueue(^{ + + [self commitCompostionToWorkspace]; + + for (WAAVSEComposition *composition in self.workSpace) { + WAAVSERangeCommand *rangeCommand = [[WAAVSERangeCommand alloc] initWithComposition:composition]; + [rangeCommand performWithAsset:composition.mutableComposition timeRange:range]; + } + }); + + return YES; +} + +- (BOOL)rotateVideoByDegress:(NSInteger)degress{ + + runAsynchronouslyOnVideoContextQueue(^{ + + [self commitCompostionToWorkspace]; + + for (WAAVSEComposition *composition in self.workSpace) { + WAAVSERotateCommand *rotateCommand = [[WAAVSERotateCommand alloc] initWithComposition:composition]; + [rotateCommand performWithAsset:composition.mutableComposition degress:degress]; + } + + }); + + return YES; + +} + +- (BOOL)appendWaterMark:(UIImage *)waterImg relativeRect:(CGRect)relativeRect{ + + if (!waterImg) { + return NO; + } + + runAsynchronouslyOnVideoContextQueue(^{ + + [self commitCompostionToWorkspace]; + + for (WAAVSEComposition *composition in self.workSpace) { + WAAVSEImageMixCommand *command = [[WAAVSEImageMixCommand alloc] initWithComposition:composition]; + command.imageBg = NO; + command.image = waterImg; + [command imageLayerRectWithVideoSize:^CGRect(CGSize videoSize) { + return CGRectMake(videoSize.width * relativeRect.origin.x,videoSize.height * (1 - relativeRect.origin.y) - videoSize.height * relativeRect.size.height,videoSize.width * relativeRect.size.width, videoSize.height * relativeRect.size.height); + }]; + [command performWithAsset:composition.mutableComposition]; + } + + }); + + return YES; +} + +#pragma mark 变速 +- (BOOL)gearBoxWithScale:(CGFloat)scale{ + + runAsynchronouslyOnVideoContextQueue(^{ + [self commitCompostionToWorkspace]; + + for (WAAVSEComposition *composition in self.workSpace) { + WAAVSEGearboxCommand *gearBox = [[WAAVSEGearboxCommand alloc] initWithComposition:composition]; + [gearBox performWithAsset:composition.mutableComposition scale:scale]; + } + }); + return YES; +} + +- (BOOL)gearBoxTimeByScaleArray:(NSArray *)scaleArray{ + + if (!scaleArray.count) { + return NO; + } + + + runAsynchronouslyOnVideoContextQueue(^{ + + [self commitCompostionToWorkspace]; + + + for (WAAVSEComposition *composition in self.workSpace) { + + WAAVSEGearboxCommand *gearBox = [[WAAVSEGearboxCommand alloc] initWithComposition:composition]; + [gearBox performWithAsset:composition.mutableComposition models:scaleArray]; + } + }); + + return YES; +} + +#pragma mark 换音 +- (BOOL)replaceSoundBySoundPath:(NSString *)soundPath{ + + if (![[NSFileManager defaultManager] fileExistsAtPath:soundPath]) { + return NO; + } + + AVAsset *soundAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:soundPath] options:nil]; + if (!soundAsset.playable) { + return NO; + } + runAsynchronouslyOnVideoContextQueue(^{ + [self commitCompostionToWorkspace]; + + for (WAAVSEComposition *composition in self.workSpace) { + WAAVSEReplaceSoundCommand *replaceCommand = [[WAAVSEReplaceSoundCommand alloc] initWithComposition:composition]; + [replaceCommand performWithAsset:composition.mutableComposition replaceAsset:soundAsset]; + } + }); + + + return YES; +} + +#pragma mark 混音 +- (BOOL)dubbedSoundBySoundPath:(NSString *)soundPath{ + + return [self dubbedSoundBySoundPath:soundPath volume:0.5 mixVolume:0.5 insertTime:0]; +} + +- (BOOL)dubbedSoundBySoundPath:(NSString *)soundPath volume:(CGFloat)volume mixVolume:(CGFloat)mixVolume insertTime:(CGFloat)insetDuration{ + + if (![[NSFileManager defaultManager] fileExistsAtPath:soundPath]) { + return NO; + } + + AVAsset *soundAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:soundPath] options:nil]; + if (!soundAsset.playable) { + return NO; + } + + runAsynchronouslyOnVideoContextQueue(^{ + [self commitCompostionToWorkspace]; + for (WAAVSEComposition *composition in self.workSpace) { + WAAVSEDubbedCommand *command = [[WAAVSEDubbedCommand alloc] initWithComposition:composition]; + command.insertTime = CMTimeMakeWithSeconds(insetDuration, composition.mutableComposition.duration.timescale); + command.audioVolume = volume; + command.mixVolume = mixVolume; + [command performWithAsset:composition.mutableComposition mixAsset:soundAsset]; + } + }); + + return YES; +} + +#pragma mark video edit + +- (void)syncFinishEditByFilePath:(NSString *)filePath complete:(void (^)(NSError *))complete{ + runSynchronouslyOnVideoContextQueue(^{ + [self finishEditByFilePath:filePath complete:complete]; + }); +} + +- (void)asyncFinishEditByFilePath:(NSString *)filePath complete:(void (^)(NSError *))complete{ + runAsynchronouslyOnVideoContextQueue(^{ + [self finishEditByFilePath:filePath complete:complete]; + }); +} + +- (void)cancelEdit{ + + runSynchronouslyOnVideoProcessingQueue(^{ + self.cancel = YES; + if (self.exportCommand.exportSession.status == AVAssetExportSessionStatusExporting) { + [self.exportCommand.exportSession cancelExport]; + NSLog(@"%s",__func__); + } + }); +} + +- (void)clean{ + + runAsynchronouslyOnVideoContextQueue(^{ + [self __internalClean]; + }); + +} + +- (void)__internalClean{ + + for (NSString *tmpPath in self.tmpVideoSpace) { + if ([[NSFileManager defaultManager] fileExistsAtPath:tmpPath]) { + [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:nil]; + } + } + + [self.tmpVideoSpace removeAllObjects]; + [self.workSpace removeAllObjects]; + [self.composeSpace removeAllObjects]; + self.editorComplete = nil; + self.filePath = nil; + self.tmpPath = nil; + + self.directCompostionIndex = 0; + +} + +#pragma mark private +- (void)commitCompostionToWorkspace{ + if (self.cacheComposition) { + [self.workSpace addObject:self.cacheComposition]; + self.cacheComposition = nil; + } +} + +- (void)commitCompostionToComposespace{ + + if (!self.workSpace.count) { + return; + } + + // workspace的最后一个compostion可寻求合并 + for (int i = 0; i < self.workSpace.count - 1; i++) { + [self.composeSpace addObject:self.workSpace[i]]; + } + + WAAVSEComposition *currentComposition = [self.workSpace lastObject]; + + [self.workSpace removeAllObjects]; + + if (!currentComposition.mutableVideoComposition && !currentComposition.mutableAudioMix && self.composeSpace.count == self.directCompostionIndex) { // 可以直接合并 + if (self.composeSpace.count > 0) { + WAAVSEComposition *compositon = [self.composeSpace lastObject]; + + WAAVSEVideoMixCommand *mixCommand = [[WAAVSEVideoMixCommand alloc] initWithComposition:compositon]; + [mixCommand performWithAsset:compositon.mutableComposition mixAsset:(AVAsset *)currentComposition.mutableComposition]; + }else{ + self.directCompostionIndex = self.composeSpace.count; + [self.composeSpace addObject:currentComposition]; + } + }else{ + [self.composeSpace addObject:currentComposition]; + } + +} + +- (void)processVideoByComposition:(WAAVSEComposition *)composition{ + + NSString *filePath = self.filePath; + if(self.composeSpace.count != 1 || self.tmpVideoSpace.count){ + self.tmpPath = filePath = [self tmpVideoFilePath]; + } + + WAAVSEExportCommand *exportCommand = [[WAAVSEExportCommand alloc] initWithComposition:composition]; + self.exportCommand = exportCommand; + [exportCommand performSaveByPath:filePath]; + + +} + +- (void)successToProcessCurrentCompostion{ + + [self.composeSpace removeObjectAtIndex:0]; + [self.tmpVideoSpace addObject:self.tmpPath]; + + if (self.composeSpace.count > 0) { + [self processVideoByComposition:self.composeSpace.firstObject]; + }else{ + self.tmpPath = nil; + + WAAVSEVideoMixCommand *mixComand = [WAAVSEVideoMixCommand new]; + + NSMutableArray *assetAry = [NSMutableArray array]; + for (NSString *filePath in self.tmpVideoSpace) { + [assetAry addObject:[AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]]; + } + [mixComand performWithAssets:assetAry]; + mixComand.composition.presetName = self.presetName; + mixComand.composition.videoQuality = self.videoQuality; + + WAAVSEExportCommand *exportCommand = [[WAAVSEExportCommand alloc] initWithComposition:mixComand.composition]; + self.exportCommand = exportCommand; + [exportCommand performSaveByPath:self.filePath]; + + } +} + +- (void)failToProcessVideo:(NSError *)error{ + + // 清理失败文件 + if ([[NSFileManager defaultManager] fileExistsAtPath:self.filePath]) { + [[NSFileManager defaultManager] removeItemAtPath:self.filePath error:nil]; + } + + if (self.editorComplete) { + self.editorComplete(error); + } + + [self __internalClean]; + + if (self.suspend) { + self.suspend = NO; + dispatch_resume(_contextQueue); + } + +} + +- (void)successToProcessVideo{ + + if (self.editorComplete) { + void (^editorComplete)(NSError *error) = self.editorComplete; + dispatch_async(dispatch_get_main_queue(), ^{ + editorComplete(nil); + }); + } + [self __internalClean]; + + if (self.suspend) { + self.suspend = NO; + dispatch_resume(_contextQueue); + } + +} + +- (void)finishEditByFilePath:(NSString *)filePath complete:(void (^)(NSError *error))complete{ + + [self commitCompostionToWorkspace]; + + [self commitCompostionToComposespace]; + + if (!self.composeSpace.count) { + complete([NSError errorWithDomain:AVFoundationErrorDomain code:AVErrorNoDataCaptured userInfo:nil]); + return; + } + + self.filePath = filePath; + self.editorComplete = complete; + + runSynchronouslyOnVideoProcessingQueue(^{ + + self.suspend = YES; + dispatch_suspend(_contextQueue); + + if (self.cancel) { + [self failToProcessVideo:[NSError errorWithDomain:AVFoundationErrorDomain code:-10000 userInfo:@{NSLocalizedFailureReasonErrorKey:@"User cancel process!"}]]; + return ; + }else{ + [self processVideoByComposition:self.composeSpace.firstObject]; + return ; + } + }); + + +} + +- (NSString *)tmpVideoFilePath{ + return [_tmpDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%f.mp4",[NSDate timeIntervalSinceReferenceDate]]]; +} + +#pragma mark notification +- (void)AVEditorNotification:(NSNotification *)notification{ + + runAsynchronouslyOnVideoProcessingQueue(^{ + if ([[notification name] isEqualToString:WAAVSEExportCommandCompletionNotification] && self.exportCommand == notification.object) { + + NSError *error = [notification.userInfo objectForKey:WAAVSEExportCommandError]; + + if (self.cancel) { + error = [NSError errorWithDomain:AVFoundationErrorDomain code:-10000 userInfo:@{NSLocalizedFailureReasonErrorKey:@"User cancel process!"}]; + } + + if (error) { + [self failToProcessVideo:error]; + }else{ + if(!self.tmpPath){// 成功合成 + [self successToProcessVideo]; + }else{ + [self successToProcessCurrentCompostion]; + } + } + + } + }); + +} + + +#pragma mark getter and setter +- (void)setRatio:(WAVideoExportRatio)ratio{ + + if (self.workSpace.count) { + return; + } + + _ratio = ratio; + switch (self.ratio) { + case WAVideoExportRatio640x480: + self.presetName = AVAssetExportPreset640x480; + break; + case WAVideoExportRatio960x540: + self.presetName = AVAssetExportPreset960x540; + break; + case WAVideoExportRatio1280x720: + self.presetName = AVAssetExportPreset1280x720; + break; + case WAVideoExportRatioHighQuality: + self.presetName = AVAssetExportPresetHighestQuality; + break; + case WAVideoExportRatioMediumQuality: + self.presetName = AVAssetExportPresetMediumQuality; + break; + case WAVideoExportRatioLowQuality: + self.presetName = AVAssetExportPresetLowQuality; + break; + default: + break; + } +} + +#pragma mark getter +- (NSMutableArray *)composeSpace{ + if (!_composeSpace) { + if (!_composeSpace) { + _composeSpace = [NSMutableArray array]; + } + } + return _composeSpace; +} + +- (NSMutableArray *)workSpace{ + if (!_workSpace) { + if (!_workSpace) { + _workSpace = [NSMutableArray array]; + } + } + return _workSpace; +} + +- (NSMutableArray *)tmpVideoSpace{ + if (!_tmpVideoSpace) { + if (!_tmpVideoSpace) { + _tmpVideoSpace = [NSMutableArray array]; + } + } + return _tmpVideoSpace; +} +@end diff --git a/WAVideoBox/main.m b/WAVideoBox/main.m new file mode 100644 index 0000000..649beb1 --- /dev/null +++ b/WAVideoBox/main.m @@ -0,0 +1,16 @@ +// +// main.m +// WAVideoBox +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/WAVideoBoxTests/Info.plist b/WAVideoBoxTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/WAVideoBoxTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/WAVideoBoxTests/WAVideoBoxTests.m b/WAVideoBoxTests/WAVideoBoxTests.m new file mode 100644 index 0000000..fd328d9 --- /dev/null +++ b/WAVideoBoxTests/WAVideoBoxTests.m @@ -0,0 +1,37 @@ +// +// WAVideoBoxTests.m +// WAVideoBoxTests +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import + +@interface WAVideoBoxTests : XCTestCase + +@end + +@implementation WAVideoBoxTests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/WAVideoBoxUITests/Info.plist b/WAVideoBoxUITests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/WAVideoBoxUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/WAVideoBoxUITests/WAVideoBoxUITests.m b/WAVideoBoxUITests/WAVideoBoxUITests.m new file mode 100644 index 0000000..bc30277 --- /dev/null +++ b/WAVideoBoxUITests/WAVideoBoxUITests.m @@ -0,0 +1,38 @@ +// +// WAVideoBoxUITests.m +// WAVideoBoxUITests +// +// Created by 黄锐灏 on 2019/1/6. +// Copyright © 2019 黄锐灏. All rights reserved. +// + +#import + +@interface WAVideoBoxUITests : XCTestCase + +@end + +@implementation WAVideoBoxUITests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + self.continueAfterFailure = NO; + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + [[[XCUIApplication alloc] init] launch]; + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +@end