Skip to content

Instantly share code, notes, and snippets.

@thanhzusu
Forked from acj/TrimVideo.swift
Created October 3, 2021 05:16
Show Gist options
  • Save thanhzusu/d27b6aef91da65b6a2ba870b7cca65c4 to your computer and use it in GitHub Desktop.
Save thanhzusu/d27b6aef91da65b6a2ba870b7cca65c4 to your computer and use it in GitHub Desktop.
Trim video using AVFoundation in Swift
//
// TrimVideo.swift
// VideoLab
//
// Created by Adam Jensen on 3/28/15.
// Updated for Swift 5 (tested with Xcode 10.3) on 7/30/19.
// MIT license
//
import AVFoundation
import Foundation
typealias TrimCompletion = (Error?) -> ()
typealias TrimPoints = [(CMTime, CMTime)]
func verifyPresetForAsset(preset: String, asset: AVAsset) -> Bool {
let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: asset)
let filteredPresets = compatiblePresets.filter { $0 == preset }
return filteredPresets.count > 0 || preset == AVAssetExportPresetHighestQuality
}
func removeFileAtURLIfExists(url: NSURL) {
if let filePath = url.path {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
do {
try fileManager.removeItem(atPath: filePath)
}
catch {
print("Couldn't remove existing destination file: \(error)")
}
}
}
}
func trimVideo(sourceURL: URL, destinationURL: URL, trimPoints: TrimPoints, completion: TrimCompletion?) {
assert(sourceURL.isFileURL)
assert(destinationURL.isFileURL)
let options = [ AVURLAssetPreferPreciseDurationAndTimingKey: true ]
let asset = AVURLAsset(url: sourceURL, options: options)
let preferredPreset = AVAssetExportPresetHighestQuality
if verifyPresetForAsset(preset: preferredPreset, asset: asset) {
let composition = AVMutableComposition()
guard
let videoCompTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID()),
let audioCompTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())
else {
let error = NSError(domain: "org.linuxguy.VideoLab", code: -1, userInfo: [NSLocalizedDescriptionKey: "Couldn't add mutable tracks"])
completion?(error)
return
}
guard
let assetVideoTrack = asset.tracks(withMediaType: AVMediaType.video).first,
let assetAudioTrack = asset.tracks(withMediaType: AVMediaType.audio).first
else {
let error = NSError(domain: "org.linuxguy.VideoLab", code: -1, userInfo: [NSLocalizedDescriptionKey: "Couldn't find video or audio track in source asset"])
completion?(error)
return
}
// Preserve the orientation of the source asset
videoCompTrack.preferredTransform = assetVideoTrack.preferredTransform
var accumulatedTime = CMTime.zero
for (startTimeForCurrentSlice, endTimeForCurrentSlice) in trimPoints {
let durationOfCurrentSlice = CMTimeSubtract(endTimeForCurrentSlice, startTimeForCurrentSlice)
let timeRangeForCurrentSlice = CMTimeRangeMake(start: startTimeForCurrentSlice, duration: durationOfCurrentSlice)
do {
try videoCompTrack.insertTimeRange(timeRangeForCurrentSlice, of: assetVideoTrack, at: accumulatedTime)
try audioCompTrack.insertTimeRange(timeRangeForCurrentSlice, of: assetAudioTrack, at: accumulatedTime)
}
catch {
let error = NSError(domain: "org.linuxguy.VideoLab", code: -1, userInfo: [NSLocalizedDescriptionKey: "Couldn't insert time ranges: \(error)"])
completion?(error)
return
}
accumulatedTime = CMTimeAdd(accumulatedTime, durationOfCurrentSlice)
}
guard let exportSession = AVAssetExportSession(asset: composition, presetName: preferredPreset) else {
let error = NSError(domain: "org.linuxguy.VideoLab", code: -1, userInfo: [NSLocalizedDescriptionKey: "Couldn't create export session"])
completion?(error)
return
}
exportSession.outputURL = destinationURL
exportSession.outputFileType = AVFileType.mp4
exportSession.shouldOptimizeForNetworkUse = true
removeFileAtURLIfExists(url: destinationURL as NSURL)
exportSession.exportAsynchronously(completionHandler: {
completion?(exportSession.error)
})
} else {
let error = NSError(domain: "org.linuxguy.VideoLab", code: -1, userInfo: [NSLocalizedDescriptionKey: "Could not find a suitable export preset for the input video"])
if let completion = completion {
completion(error)
return
}
}
}
//
// Example usage from a Swift playground
//
import PlaygroundSupport
let sourceURL = playgroundSharedDataDirectory.appendingPathComponent("TestVideo.mov")
let destinationURL = playgroundSharedDataDirectory.appendingPathComponent("TrimmedVideo.mp4")
let timeScale: Int32 = 1000
let trimPoints = [(CMTimeMake(value: 2000, timescale: timeScale), CMTimeMake(value: 5000, timescale: timeScale)),
(CMTimeMake(value: 20500, timescale: timeScale), CMTimeMake(value: 23000, timescale: timeScale)),
(CMTimeMake(value: 60000, timescale: timeScale), CMTimeMake(value: 65000, timescale: timeScale))]
trimVideo(sourceURL: sourceURL, destinationURL: destinationURL, trimPoints: trimPoints) { error in
if let error = error {
print("Failure: \(error)")
} else {
print("Success")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment