728x90
이전 포스팅
출처
이전까지 구현한 것
- 포토앨범에서 비디오 불러와 재생하기
- 카메라로 비디오 찍은 후 포토앨범에 저장하기
- 병합할 비디오1, 2 와 음악 불러오기
목표
- 비디오 2개와 음악 합치기
- 비디오는 2개를 연결, 그 위에 음악 올리기
*실제 기기에서 실행하려면 Privacy 에 관한 설명을 info.plist 에 써줘야 한다.
Assets(비디오, 오디오) 병합 completion 함수 작성
병합할 때 중간에 돌아가는 로딩 indicator 를 추가해 준다.
import Photos
func exportDidFinish(_ session: AVAssetExportSession) {
// 1
activityMonitor.stopAnimating()
firstAsset = nil
secondAsset = nil
audioAsset = nil
// 2
guard
session.status == AVAssetExportSession.Status.completed,
let outputURL = session.outputURL
else { return }
// 3
let saveVideoToPhotos = {
// 4
let changes: () -> Void = {
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
}
PHPhotoLibrary.shared().performChanges(changes) { saved, error in
DispatchQueue.main.async {
let success = saved && (error == nil)
let title = success ? "Success" : "Error"
let message = success ? "Video saved" : "Failed to save video"
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(
title: "OK",
style: UIAlertAction.Style.cancel,
handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
// 5
if PHPhotoLibrary.authorizationStatus() != .authorized {
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
saveVideoToPhotos()
}
}
} else {
saveVideoToPhotos()
}
}
- 위 함수는 Export 가 끝났을 때 호출되는 함수이다.
- 함수가 끝나면 activityMonitor를 멈추고 (로딩 indicator)
- 사용한 Asset 들을 초기화 한다. (nil할당)
- session.status를 통해 export 가 완료되었는지 확인 한 뒤 완료된 파일의 url 을 outputURL 에 저장한다.
- PHPhotoLibrary를 통해 (파일 접근과 같은 객체는 singleton패턴으로 shared를 통해 일관성을 유지한다) 비디오를 저장한다.
- 비디오가 저장되면 AlertController로 완료되었다고 알려준다.
- 마지막은 만약 포토앨범 권한이 없으면 먼저 권한 요청부터 하는 코드이다.
실제 Asset 병합 코드 작성
코드가 많이 기니까 주석으로 설명하겠다.
func merge(){
//비디오 두개가 선택되었는지 guard 문으로 확인해본다.
guard
let firstAsset = firstAsset,
let secondAsset = secondAsset
else { return }
//로딩 indicator 를 시작한다.
activityMonitor.startAnimating()
// 1 비디오와 오디오 트랙을 잠시 저장해둘 AVMutableComposition을 선언한다.
let mixComposition = AVMutableComposition()
// 2 해당 AVMutableComposition에 첫번째 비디오 트랙을 추가한다.
guard
let firstTrack = mixComposition.addMutableTrack(
withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else { return }
// 3 insertTimeRange를 통해 첫번째 비디오의 어떤 부분까지 넣을지 정한다. (코드는 전체부분입력)
do {
try firstTrack.insertTimeRange(
CMTimeRangeMake(start: .zero, duration: firstAsset.duration),
of: firstAsset.tracks(withMediaType: .video)[0],
at: .zero)
} catch {
print("Failed to load first track")
return
}
// 4 동일한 방법으로 두번째 비디오를 추가하고 어느 부분을 넣을지 정해준다. (zero ~ duration 이니 전체)
guard
let secondTrack = mixComposition.addMutableTrack(
withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else { return }
do {
try secondTrack.insertTimeRange(
CMTimeRangeMake(start: .zero, duration: secondAsset.duration),
of: secondAsset.tracks(withMediaType: .video)[0],
at: firstAsset.duration)
} catch {
print("Failed to load second track")
return
}
// 5 두개의 AVMutableCompisitionTrace 인스턴스를 준비했으니
// 6 AVMutableVideoCompositionLayerInstruction을 통해 각각의 트랙에 편집이 가능하도록 만든다.
//mainInstruction을 설정해 편집된 비디오의 totalTime이 first + second 비디오 재생시간의 합으로 한다.
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRangeMake(
start: .zero,
duration: CMTimeAdd(firstAsset.duration, secondAsset.duration))
// 7 각각의 asset 에 맞게 두개의 instruction을 준비한다.
// first instruction은 투명하게 만들어 두번째 비디오가 시작될 때 보여지지 않게 설정한다.
let firstInstruction = AVMutableVideoCompositionLayerInstruction(
assetTrack: firstTrack)
firstInstruction.setOpacity(0.0, at: firstAsset.duration)
let secondInstruction = AVMutableVideoCompositionLayerInstruction(
assetTrack: secondTrack)
// 8 위에서 세팅한 두개의 instruction 인스턴스를 mainInstruction에 추가해준다.
// main instruction을 AVMutableVideoComposition 의 프로퍼티에 추가해준다.
//frame rate 은 30fps이다.
mainInstruction.layerInstructions = [firstInstruction, secondInstruction]
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainComposition.renderSize = CGSize(
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
// 9
// 10 오디오 에셋을 불러온다. 시간 범위는 첫번째와 두번째 비디오 재생길이의 합으로 설정한따.
if let loadedAudioAsset = audioAsset {
let audioTrack = mixComposition.addMutableTrack(
withMediaType: .audio,
preferredTrackID: 0)
do {
try audioTrack?.insertTimeRange(
CMTimeRangeMake(
start: .zero,
duration: CMTimeAdd(
firstAsset.duration,
secondAsset.duration)),
of: loadedAudioAsset.tracks(withMediaType: .audio)[0],
at: .zero)
} catch {
print("Failed to load Audio track")
}
}
// 11 완성된 비디오를 저장하기 전에 먼저 저장될 경로가 필요하다.
//현재 시간을 활용해 unique 한 파일 이름을 설정해준다.
guard
let documentDirectory = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask).first
else { return }
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
let date = dateFormatter.string(from: Date())
let url = documentDirectory.appendingPathComponent("mergeVideo-\(date).mov")
// 12 합쳐진 비디오를 render하고 export 해야 한다. 먼저 AVAssetExportSession을 만들어
//export 설정과 함께 content 을 transcode 한다. 이전에 AVMutableVideoComposition을 설정해놨기 때문에 exporter 에 assign만 하면 된다.
guard let exporter = AVAssetExportSession(
asset: mixComposition,
presetName: AVAssetExportPresetHighestQuality)
else { return }
exporter.outputURL = url
exporter.outputFileType = AVFileType.mov
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = mainComposition
// 13 export session을 초기화 한 뒤 exportAsynchrously()를 통해 export 작업을 시작할 수 있다.
// 비동기적으로 동작하기 때문에 함수는 바로 리턴한다. completion handler를 통해 성공/실패를 전닫ㄹ한다.
exporter.exportAsynchronously {
DispatchQueue.main.async {
self.exportDidFinish(exporter)
}
}
}
- AVcomposition은 미디어 데이터를 여러 파일 소스에서 가져와 합친다.
- AVComposition의 최상단 레벨에는 각각 audio 나 video 미디어를 표현하는 track 들의 집합으로 구성되어있다.
- AVCompotisionTrack 이 각각의 트랙을 나타낸다.
- AVMutableComposition과 AVMutableCompositionTrack 도 비슷하게
composition을 만드는 고차원 수준의 인터페이스를 제공한다. - 이 객첻들은 insertion, removal, scaling 등의 operation을 제공한다.
결과
움짤이라 음악이 안나오지만 오디오도 제대로 들어갔다.
편집된 영상을 보면 해상도가 안맞거나 orientation이 맞지 않는걸 볼 수 있다.
preferredTransform을 설정해주면 되는데 그건 원본 튜토리얼의 Orienting Video 파트를 보면 알 수 있다.
[원본튜토리얼]
[프로젝트 코드]
728x90
'iOS' 카테고리의 다른 글
Github을 이용한 iOS 개발 협업 기초 (PR,코드리뷰,Merge) (10) | 2020.09.09 |
---|---|
🎥 iOS 비디오에 Overlay와 Animation 추가하기 (0) | 2020.09.02 |
🎥 iOS 비디오 재생, 녹화, 병합 튜토리얼 (1) (0) | 2020.08.31 |
iOS 영상편집 앱 개발을 위한 레퍼런스 모음 (0) | 2020.08.30 |
iOS 앱 이름 한국어 설정 (4) | 2020.08.29 |
댓글