Files

357 lines
13 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// ViewController.swift
// SimpleVideoEditor
//
// Created by RenZhu Macro on 2020/7/2.
// Copyright © 2020 RenZhu Macro. All rights reserved.
//
import UIKit
import AVFoundation
import MobileCoreServices
import MetalVideoProcess
class ViewController: UIViewController {
@IBOutlet weak var renderView: MetalVideoProcessRenderView!
@IBOutlet weak var mainEditButton: UIButton!
@IBOutlet weak var mainDeleteButton: UIButton!
@IBOutlet weak var subEditButton: UIButton!
@IBOutlet weak var subDeleteButton: UIButton!
var mainPicker: UIImagePickerController?
var subPicker: UIImagePickerController?
var mainLayer: MetalVideoProcessBlendFilter?
var player: MetalVideoProcessPlayer?
var movieWriter: MetalVideoProcessMovieWriter?
var videoBackground: MetalVideoProcessBackground = MetalVideoProcessBackground(trackID: 0)
var pop: PopupDialog?
var progressHUD: MBProgressHUD?
var mainResources: [ResourceItem] = []
var lastMainLayerItem: ResourceItem?
@IBOutlet weak var mainTableView: UITableView!
var mainDragger: TableViewDragger?
var videoEditor: MetalVideoEditor?
var mainSelectedItem: ResourceItem?
var subResources: [ResourceItem] = []
@IBOutlet weak var subTableView: UITableView!
var subDragger: TableViewDragger?
var subSelectedItem: ResourceItem?
@IBOutlet weak var progress: UISlider!
private var rotGes: UIRotationGestureRecognizer?
private var pinchGes: UIPinchGestureRecognizer?
var currentPostion: Position = Position(0.0, 0.0)
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(true, animated: false)
self.mainTableView.register(ResourceItemTableViewCell.self, forCellReuseIdentifier: "mainCell")
self.mainTableView.allowsSelection = true
self.mainTableView.dataSource = self
self.mainTableView.delegate = self
self.subTableView.register(ResourceItemTableViewCell.self, forCellReuseIdentifier: "pipCell")
self.subTableView.allowsSelection = true
self.subTableView.dataSource = self
self.subTableView.delegate = self
self.mainDragger = TableViewDragger(tableView: self.mainTableView)
self.mainDragger?.delegate = self
self.subDragger = TableViewDragger(tableView: self.subTableView)
self.subDragger?.delegate = self
let asset1 = AVAsset(url: Bundle.main.url(forResource: "cute", withExtension: "mp4")!)
let asset2 = AVAsset(url: Bundle.main.url(forResource: "movie", withExtension: "mov")!)
let sampleMainItem = ResourceItem(asset: asset1)
let sampleSubItem = ResourceItem(asset: asset2)
self.lastMainLayerItem = sampleMainItem
let backgroundFrame = self.renderView.frame
let backgroundSize = CGSize(width: backgroundFrame.size.width * 2.0,
height: backgroundFrame.size.height * 2.0)
MetalVideoProcessBackground.canvasSize = backgroundSize
videoBackground.canvasSizeType = .Type720p
videoBackground.aspectRatioType = .Ratio16_9
videoBackground.setBackgroundType(type: .Blur)
self.mainResources.append(sampleMainItem)
self.subResources.append(sampleSubItem)
self.progress.minimumValue = 0.0
sampleMainItem.fillType = .aspectToFit
sampleMainItem.roi = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
sampleSubItem.fillType = .aspectToFit
sampleSubItem.roi = CGRect(x: 0.5, y: 0.5, width: 0.5, height: 0.5)
self.rebuildPipeline()
self.mainTableView.reloadData()
self.subTableView.reloadData()
self.renderView.isUserInteractionEnabled = true
rotGes = UIRotationGestureRecognizer(target: self,
action: #selector(self.rotateAction))
rotGes!.delegate = self;
pinchGes = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchAction))
pinchGes!.delegate = self;
let panGes = UIPanGestureRecognizer(target: self, action: #selector(self.panAction))
panGes.delegate = self
self.renderView.addGestureRecognizer(rotGes!)
self.renderView.addGestureRecognizer(pinchGes!)
self.renderView.addGestureRecognizer(panGes)
}
@IBAction func changeFillType(_ sender: UIButton) {
let tag = sender.tag
switch tag {
case 0:
self.videoBackground.aspectRatioType = .Ratio16_9
break
case 1:
self.videoBackground.aspectRatioType = .Ratio9_16
case 2:
self.videoBackground.aspectRatioType = .Ratio1_1
case 3:
self.videoBackground.aspectRatioType = .Ratio4_3
case 4:
self.videoBackground.aspectRatioType = .Ratio3_4
default:
break
}
}
@IBAction func play(_ sender: Any) {
self.player?.play()
}
@IBAction func pause(_ sender: Any) {
self.player?.pause()
}
@IBAction func importMain(_ sender: Any) {
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = .photoLibrary
var pickerTypes: [String] = []
pickerTypes.append(kUTTypeMovie as String)
picker.mediaTypes = pickerTypes
picker.allowsEditing = true
self.navigationController?.present(picker, animated: true, completion: nil)
self.mainPicker = picker
}
@IBAction func importPIP(_ sender: Any) {
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = .photoLibrary
var pickerTypes: [String] = []
pickerTypes.append(kUTTypeMovie as String)
picker.mediaTypes = pickerTypes
picker.allowsEditing = true
self.navigationController?.present(picker, animated: true, completion: nil)
self.subPicker = picker
}
@IBAction func progressChanged(_ sender: UISlider) {
let value = sender.value
self.player?.seekTo(time: Float64(value))
}
@IBAction func editItem(_ sender: UIButton) {
let resourceEditView = ResourceItemEditView(nibName: "ResourceItemEditView", bundle: nil)
self.pop = PopupDialog(viewController: resourceEditView, buttonAlignment: .horizontal, transitionStyle: .bounceDown, tapGestureDismissal: true, panGestureDismissal: false)
let btnCancel = CancelButton(title: "Cancel", height: 60) {
self.pop?.viewController.removeFromParent()
self.pop?.removeFromParent()
self.pop = nil
return
}
let btnOK = DefaultButton(title: "OK", height: 60) {
self.rebuildPipeline()
self.mainTableView.reloadData()
self.subTableView.reloadData()
self.pop?.viewController.removeFromParent()
self.pop?.removeFromParent()
self.pop = nil
return
}
pop?.addButtons([btnOK, btnCancel])
if sender == self.mainEditButton {
guard let item = self.mainSelectedItem else {
return
}
self.present(pop!
, animated: true) {
resourceEditView.loadResourceItem(item)
}
} else {
guard let item = self.subSelectedItem else {
return
}
self.present(pop!
, animated: true) {
resourceEditView.loadResourceItem(item, isPipItem: true)
}
}
}
@IBAction func deleteItem(_ sender: UIButton) {
if sender == self.mainDeleteButton {
self.mainResources.removeAll { $0.trackID == self.mainSelectedItem?.trackID ?? 0 }
} else {
self.subResources.removeAll { $0.trackID == self.subSelectedItem?.trackID ?? 0 }
}
self.rebuildPipeline()
self.mainTableView.reloadData()
self.subTableView.reloadData()
self.subEditButton.isEnabled = self.subResources.count > 0
self.subDeleteButton.isEnabled = self.subResources.count > 0
self.mainEditButton.isEnabled = self.mainResources.count > 0
self.mainDeleteButton.isEnabled = self.mainResources.count > 0
}
func rebuildPipeline() {
self.videoEditor = nil
//rebuild player
self.player?.suspend()
self.player?.dispose()
self.player?.removeAllTargets()
self.player = nil
//clear all sources
renderView.sources.sources.removeAll()
do {
let currentEditor = try MetalVideoEditor(videoItems: self.mainResources, overlayItems: self.subResources)
self.videoEditor = currentEditor
try currentEditor.updateChannel()
print("before build PlayerItem mainItems:", currentEditor.editorItems)
print("before build PlayerItem overlayItems:", currentEditor.overlayItems)
let playerItem = currentEditor.buildPlayerItem()
print("after build PlayerItem mainItems:", currentEditor.editorItems)
print("after build PlayerItem overlayItems:", currentEditor.overlayItems)
self.progress.maximumValue = Float(playerItem.duration.seconds)
let player = try MetalVideoProcessPlayer(playerItem: playerItem)
let mainLayer = MetalVideoProcessBlendFilter()
mainLayer.debugName = "mainLayer"
var lastTransform: MetalVideoProcessTransformFilter? = nil
for mainItem in self.mainResources {
let transform = MetalVideoProcessTransformFilter()
transform.saveUniformSettings(forTimelineRange: mainItem.timeRange, trackID: mainItem.trackID)
transform.roi = mainItem.roi
transform.debugName = "main transform trackId:\(mainItem.trackID)"
//render blur background with every main track
player.addTarget(videoBackground, atTargetIndex: nil, trackID: mainItem.trackID, targetTrackId: 0)
mainItem.transformFilter = transform
transform.rotate = mainItem.rotate
transform.translation = mainItem.translation
transform.scale = mainItem.scale
mainItem.currentLayer = mainLayer
if let lt = lastTransform {
//link every main transform in mainLayer
lt --> transform
}
lastTransform = transform
}
// make sure mapping every items trackID on main track to main trackContainer with trackID 0,
// if you don't do this, player can't render every item's texutre to item's target
if let firstTransform = self.mainResources.first?.transformFilter {
self.mainResources.forEach { (item) in
player.addTarget(firstTransform, atTargetIndex: nil, trackID: item.trackID, targetTrackId: 0)
}
}
guard let lt = lastTransform else {
// zero items
self.player?.suspend()
self.player?.dispose()
self.player?.removeAllTargets()
self.player = nil
return
}
videoBackground --> mainLayer
// place lt on videoBackground, secondary source to mainLayer
lt --> mainLayer
var lastLayer: MetalVideoProcessBlendFilter = mainLayer
for subItem in self.subResources {
let pipLayer = MetalVideoProcessBlendFilter()
// render pipLayer in subItem.timeRange
pipLayer.saveUniformSettings(forTimelineRange: subItem.timeRange, trackID: subItem.trackID)
let transform = MetalVideoProcessTransformFilter()
// transform subItem's texture in subItem.timeRange
transform.saveUniformSettings(forTimelineRange: subItem.timeRange, trackID: subItem.trackID)
transform.roi = subItem.roi
// create a new targetContainer for subItem & pipLayer
player.addTarget(transform, atTargetIndex: nil, trackID: subItem.trackID, targetTrackId: subItem.trackID)
subItem.currentLayer = pipLayer
subItem.transformFilter = transform
transform.rotate = subItem.rotate
transform.translation = subItem.translation
transform.scale = subItem.scale
//compositing two layer (warning!!! place transform to secondary source to pipLayer)
lastLayer --> pipLayer
transform --> pipLayer
lastLayer = pipLayer
}
//render the final layer
lastLayer --> renderView
player.playerDelegate = self
self.player = player
} catch {
}
}
}