Skip to content

使用文档

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

Clone this wiki locally