Skip to content

Instantly share code, notes, and snippets.

@seyhagithub
Forked from acj/TrimVideo.swift
Last active December 13, 2018 03:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seyhagithub/9bb1451197547e46d548655f230320dd to your computer and use it in GitHub Desktop.
Save seyhagithub/9bb1451197547e46d548655f230320dd 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.
// Copyright (c) 2015 Adam Jensen. All rights reserved.
//
import AVFoundation
import Foundation
typealias TrimCompletion = (NSError?) -> ()
typealias TrimPoints = [(CMTime, CMTime)]
func verifyPresetForAsset(preset: String, asset: AVAsset) -> Bool {
let compatiblePresets = AVAssetExportSession.exportPresetsCompatibleWithAsset(asset) as! [String]
let filteredPresets = compatiblePresets.filter { $0 == preset }
return filteredPresets.count > 0 || preset == AVAssetExportPresetPassthrough
}
func removeFileAtURLIfExists(url: NSURL) {
if let filePath = url.path {
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(filePath) {
var error: NSError?
if !fileManager.removeItemAtPath(filePath, error: &error) {
NSLog("Couldn't remove existing destination file: \(error)")
}
}
}
}
func trimVideo(sourceURL: NSURL, destinationURL: NSURL, trimPoints: TrimPoints, completion: TrimCompletion?) {
assert(sourceURL.fileURL)
assert(destinationURL.fileURL)
let options = [ AVURLAssetPreferPreciseDurationAndTimingKey: true ]
let asset = AVURLAsset(URL: sourceURL, options: options)
let preferredPreset = AVAssetExportPresetPassthrough
if verifyPresetForAsset(preferredPreset, asset) {
let composition = AVMutableComposition()
let videoCompTrack = composition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID())
let audioCompTrack = composition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
let assetVideoTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo).first as! AVAssetTrack
let assetAudioTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeAudio).first as! AVAssetTrack
var compError: NSError?
var accumulatedTime = kCMTimeZero
for (startTimeForCurrentSlice, endTimeForCurrentSlice) in trimPoints {
let durationOfCurrentSlice = CMTimeSubtract(endTimeForCurrentSlice, startTimeForCurrentSlice)
let timeRangeForCurrentSlice = CMTimeRangeMake(startTimeForCurrentSlice, durationOfCurrentSlice)
videoCompTrack.insertTimeRange(timeRangeForCurrentSlice, ofTrack: assetVideoTrack, atTime: accumulatedTime, error: &compError)
audioCompTrack.insertTimeRange(timeRangeForCurrentSlice, ofTrack: assetAudioTrack, atTime: accumulatedTime, error: &compError)
if compError != nil {
NSLog("error during composition: \(compError)")
if let completion = completion {
completion(compError)
}
}
accumulatedTime = CMTimeAdd(accumulatedTime, durationOfCurrentSlice)
}
let exportSession = AVAssetExportSession(asset: composition, presetName: preferredPreset)
exportSession.outputURL = destinationURL
exportSession.outputFileType = AVFileTypeAppleM4V
exportSession.shouldOptimizeForNetworkUse = true
removeFileAtURLIfExists(destinationURL)
exportSession.exportAsynchronouslyWithCompletionHandler({ () -> Void in
if let completion = completion {
completion(exportSession.error)
}
})
} else {
NSLog("Could not find a suitable export preset for the input video")
let error = NSError(domain: "org.linuxguy.VideoLab", code: -1, userInfo: nil)
if let completion = completion {
completion(error)
}
}
}
let sourceURL = NSURL(fileURLWithPath: "/Users/acj/scratch/TestVideo.mp4")
let destinationURL = NSURL(fileURLWithPath: "/Users/acj/scratch/TrimmedVideo.mp4")
let sem = dispatch_semaphore_create(0)
if let sourceURL = sourceURL, destinationURL = destinationURL {
let timeScale: Int32 = 1000
let trimPoints = [(CMTimeMake(2000, timeScale), CMTimeMake(5000, timeScale)),
(CMTimeMake(20500, timeScale), CMTimeMake(23000, timeScale)),
(CMTimeMake(60000, timeScale), CMTimeMake(65000, timeScale))]
trimVideo(sourceURL, destinationURL, trimPoints) { error in
if let error = error {
NSLog("Failure: \(error)")
} else {
NSLog("Success")
}
dispatch_semaphore_signal(sem)
}
} else {
NSLog("error: Please check the source and destination paths.")
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
Swift 4 version:
import AVFoundation
import Foundation
import UIKit
class VideoTrimmer {
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 == AVAssetExportPresetPassthrough
}
func removeFileAtURLIfExists(url: URL) {
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: url.path) else { return }
do {
try fileManager.removeItem(at: url)
}
catch let error {
print("TrimVideo - Couldn't remove existing destination file: \(String(describing: error))")
}
}
func trimVideo(sourceURL: URL, destinationURL: URL, trimPoints: TrimPoints, completion: TrimCompletion?) {
guard sourceURL.isFileURL else { return }
guard destinationURL.isFileURL else { return }
let options = [
AVURLAssetPreferPreciseDurationAndTimingKey: true
]
let asset = AVURLAsset(url: sourceURL, options: options)
let preferredPreset = AVAssetExportPresetPassthrough
if verifyPresetForAsset(preset: preferredPreset, asset: asset) {
let composition = AVMutableComposition()
let videoCompTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: CMPersistentTrackID())
let audioCompTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID())
guard let assetVideoTrack: AVAssetTrack = asset.tracks(withMediaType: .video).first else { return }
guard let assetAudioTrack: AVAssetTrack = asset.tracks(withMediaType: .audio).first else { return }
var accumulatedTime = kCMTimeZero
for (startTimeForCurrentSlice, endTimeForCurrentSlice) in trimPoints {
let durationOfCurrentSlice = CMTimeSubtract(endTimeForCurrentSlice, startTimeForCurrentSlice)
let timeRangeForCurrentSlice = CMTimeRangeMake(startTimeForCurrentSlice, durationOfCurrentSlice)
do {
try videoCompTrack!.insertTimeRange(timeRangeForCurrentSlice, of: assetVideoTrack, at: accumulatedTime)
try audioCompTrack!.insertTimeRange(timeRangeForCurrentSlice, of: assetAudioTrack, at: accumulatedTime)
accumulatedTime = CMTimeAdd(accumulatedTime, durationOfCurrentSlice)
}
catch let compError {
print("TrimVideo: error during composition: \(compError)")
completion?(compError)
}
}
guard let exportSession = AVAssetExportSession(asset: composition, presetName: preferredPreset) else { return }
exportSession.outputURL = destinationURL as URL
exportSession.outputFileType = AVFileType.m4v
exportSession.shouldOptimizeForNetworkUse = true
removeFileAtURLIfExists(url: destinationURL as URL)
exportSession.exportAsynchronously {
completion?(exportSession.error)
}
}
else {
print("TrimVideo - Could not find a suitable export preset for the input video")
let error = NSError(domain: "com.bighug.ios", code: -1, userInfo: nil)
completion?(error)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment