-
-
Save seyhagithub/9bb1451197547e46d548655f230320dd to your computer and use it in GitHub Desktop.
Trim video using AVFoundation in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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