-
Notifications
You must be signed in to change notification settings - Fork 228
使用文档
Vito edited this page Jan 1, 2019
·
1 revision
这是一个基于 AVFoundation 实现的视频编辑框架,目的在于简化视频编辑的开发,让开发者容易上手实现复杂的视频编辑需求。使用这套框架开发者只需关心业务逻辑代码,无需深入底层视频编辑的实现,也不需要从头再构建一个视频编辑流程框架代码。框架在简化 API 的同时,还保留了方便扩展的接口,比如:可以提供自定义的视频资源,可以以组件的方式插入图像和音频滤镜,自定义转场效果等等。
Cabbage 有一个 Timeline 的概念, 它代表的是时间轴。所有的音视频资源都可以指定一个 TimeRange 然后放入 Timeline。
已有功能
- 图片视频
- 变速
- 倒放
- 贴纸
- 转场动画
- 关键帧动画
- 多视频同框
扩展能力
- 自定义音视频资源。继承
Resource
类并提供 AVAssetTrack 或继承ImageResource
提供 CIImage。 - 自定义图像滤镜。实现
VideoConfigurationProtocol
协议,并添加到TrackItem.configuration.videoConfiguration.configurations
里 - 自定义音频混合。实现
AudioConfigurationProtocol
协议,并添加到TrackItem.configuration.audioConfiguration.nodes
里 - 自定义视频画面转场。实现
VideoTransition
协议,TrackItem
类中可以设置转场,表示这个 TrackItem 和时间顺序的下一个 TrackItem 之间的转场 - 自定义音频转场。实现
AudioTransition
协议,设置方式同上。 - 自定义贴纸。贴纸可以创建
ImageOverlayItem
并添加到 Timeline 上。ImageOverlayItem
中可以传入ImageResource
,可以复用已经实现的ImageResource
子类 - 自定义关键帧动画。关键帧动画其实是一个自定义图像滤镜,实现了关键帧的类为
KeyframeVideoConfiguration
,它实现了VideoConfigurationProtocol
协议。关键帧动画的值类型是可以自定义的,只要是实现了KeyframeValue
协议的类,都可以作为关键帧动画的插值。OpacityKeyframeValue
是一个实现了 alpha 值做关键帧动画的具体实现。
下面是一个最简单的使用示例,使用 AVAsset 作为 Timline 的资源,并设置了画布大小和原视频基于画布的缩放模式。
// 1. 创建资源。资源还包括:`ImageResource`、`PHAssetImageResource`、`PHAssetTrackResource`、`AVAssetTrackResource`、`AVAssetReverseImageResource`、`AVAssetReaderImageResource`
let asset: AVAsset = ...
let resource = AVAssetTrackResource(asset: asset)
// 2. 创建资源配置对象 TrackItem instance, TrackItem 可以对音频和视频画面进行设置
let trackItem = TrackItem(resource: resource)
// 设置画面在画布中 aspectFill 的方式填充
trackItem.configuration.videoConfiguration.baseContentMode = .aspectFill
// 3. 放入 Timeline
let timeline = Timeline()
timeline.videoChannel = [trackItem]
timeline.audioChannel = [trackItem]
// 4. 创建 CompositionGenerator,可以用于创建 AVAssetExportSession/AVAssetImageGenerator/AVPlayerItem
let compositionGenerator = CompositionGenerator(timeline: timeline)
// 设置画布大小
compositionGenerator.renderSize = CGSize(width: 1920, height: 1080)
let exportSession = compositionGenerator.buildExportSession(presetName: AVAssetExportPresetMediumQuality)
let playerItem = compositionGenerator.buildPlayerItem()
let imageGenerator = compositionGenerator.buildImageGenerator()
贴纸
// 视频资源和配置
let asset: AVAsset = ...
let resource = AVAssetTrackResource(asset: asset)
let trackItem = TrackItem(resource: resource)
let timeline = Timeline()
timeline.videoChannel = [trackItem]
timeline.audioChannel = [trackItem]
timeline.passingThroughVideoCompositionProvider = {
let imageCompositionGroupProvider = ImageCompositionGroupProvider()
// 贴纸资源
let url = Bundle.main.url(forResource: "overlay", withExtension: "jpg")!
let image = CIImage(contentsOf: url)!
let resource = ImageResource(image: image, duration: CMTime(seconds: 3, preferredTimescale: 600))
// 贴纸配置
let imageCompositionProvider = ImageOverlayItem(resource: resource)
imageCompositionProvider.startTime = CMTime(seconds: 1, preferredTimescale: 600)
let frame = CGRect(x: 100, y: 500, width: 400, height: 400)
imageCompositionProvider.videoConfiguration.baseContentMode = .custom(frame)
return imageCompositionGroupProvider
}()
let compositionGenerator = CompositionGenerator(timeline: timeline)
compositionGenerator.renderSize = CGSize(width: 1920, height: 1080)
let exportSession = compositionGenerator.buildExportSession(presetName: AVAssetExportPresetMediumQuality)
let playerItem = compositionGenerator.buildPlayerItem()
let imageGenerator = compositionGenerator.buildImageGenerator()
转场动画
let item1: TrackItem = ...
let item2: TrackItem = ...
// 为 item1 设置转场
let transitionDuration = CMTime(seconds: 2, preferredTimescale: 600)
item1.videoTransition = PushTransition(duration: transitionDuration)
item1.audioTransition = FadeInOutAudioTransition(duration: transitionDuration)
let timeline = Timeline()
timeline.videoChannel = [item1, item2]
timeline.audioChannel = [item1, item2]
try! Timeline.reloadVideoStartTime(providers: timeline.videoChannel)
let compositionGenerator = CompositionGenerator(timeline: timeline)
compositionGenerator.renderSize = CGSize(width: 1920, height: 1080)
let playerItem = compositionGenerator.buildPlayerItem()
关键帧动画
let bambooTrackItem: TrackItem = {
let url = Bundle.main.url(forResource: "bamboo", withExtension: "mp4")!
let resource = AVAssetTrackResource(asset: AVAsset(url: url))
let trackItem = TrackItem(resource: resource)
trackItem.configuration.videoConfiguration.baseContentMode = .aspectFit
// 创建关键帧动画
let keyframeConfiguration: KeyframeVideoConfiguration<OpacityKeyframeValue> = {
let configuration = KeyframeVideoConfiguration<OpacityKeyframeValue>()
let timeValues: [(Double, CGFloat)] = [(0.0, 0), (0.5, 1.0), (2.5, 1.0), (3.0, 0.0)]
timeValues.forEach({ (time, value) in
let opacityKeyframeValue = OpacityKeyframeValue()
opacityKeyframeValue.opacity = value
let keyframe = KeyframeVideoConfiguration.Keyframe(time: CMTime(seconds: time, preferredTimescale: 600), value: opacityKeyframeValue)
configuration.insert(keyframe)
})
return configuration
}()
trackItem.configuration.videoConfiguration.configurations.append(keyframeConfiguration)
return trackItem
}()
let timeline = Timeline()
timeline.videoChannel = [bambooTrackItem]
timeline.audioChannel = [bambooTrackItem]
let compositionGenerator = CompositionGenerator(timeline: timeline)
compositionGenerator.renderSize = CGSize(width: 1920, height: 1080)
let playerItem = compositionGenerator.buildPlayerItem()
return playerItem
多视频同框
let trackItem1: TrackItem = ...
let trackItem2: TrackItem = ...
let timeline = Timeline()
timeline.videoChannel = [trackItem1]]
timeline.audioChannel = [trackItem1]
timeline.overlays = [trackItem2]
timeline.audios = [trackItem2]
let compositionGenerator = CompositionGenerator(timeline: timeline)
compositionGenerator.renderSize = renderSize
let playerItem = compositionGenerator.buildPlayerItem()
return playerItem
如果有其它问题欢迎提 issues