-
-
Save jakebromberg/098c328d87bd25ec0ae693b877cb933c 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
import AVFoundation | |
import Foundation | |
extension FileManager { | |
func removeFileIfNecessary(at url: URL) throws { | |
guard fileExists(atPath: url.path) else { | |
return | |
} | |
do { | |
try removeItem(at: url) | |
} | |
catch let error { | |
throw TrimError("Couldn't remove existing destination file: \(error)") | |
} | |
} | |
} | |
struct TrimError: Error { | |
let description: String | |
let underlyingError: Error? | |
init(_ description: String, underlyingError: Error? = nil) { | |
self.description = "TrimVideo: " + description | |
self.underlyingError = underlyingError | |
} | |
} | |
extension AVMutableComposition { | |
convenience init(asset: AVAsset) { | |
self.init() | |
for track in asset.tracks { | |
addMutableTrack(withMediaType: track.mediaType, preferredTrackID: track.trackID) | |
} | |
} | |
func trim(timeOffStart: Double) { | |
let duration = CMTime(seconds: timeOffStart, preferredTimescale: 1) | |
let timeRange = CMTimeRange(start: kCMTimeZero, duration: duration) | |
for track in tracks { | |
track.removeTimeRange(timeRange) | |
} | |
removeTimeRange(timeRange) | |
} | |
} | |
extension AVAsset { | |
func assetByTrimming(timeOffStart: Double) throws -> AVAsset { | |
let duration = CMTime(seconds: timeOffStart, preferredTimescale: 1) | |
let timeRange = CMTimeRange(start: kCMTimeZero, duration: duration) | |
let composition = AVMutableComposition() | |
do { | |
for track in tracks { | |
let compositionTrack = composition.addMutableTrack(withMediaType: track.mediaType, preferredTrackID: track.trackID) | |
try compositionTrack?.insertTimeRange(timeRange, of: track, at: kCMTimeZero) | |
} | |
} catch let error { | |
throw TrimError("error during composition", underlyingError: error) | |
} | |
return composition | |
} | |
func export(to destination: URL) throws { | |
guard let exportSession = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetPassthrough) else { | |
throw TrimError("Could not create an export session") | |
} | |
exportSession.outputURL = destination | |
exportSession.outputFileType = AVFileType.m4v | |
exportSession.shouldOptimizeForNetworkUse = true | |
let group = DispatchGroup() | |
group.enter() | |
try FileManager.default.removeFileIfNecessary(at: destination) | |
exportSession.exportAsynchronously { | |
group.leave() | |
} | |
group.wait() | |
if let error = exportSession.error { | |
throw TrimError("error during export", underlyingError: error) | |
} | |
} | |
} | |
func time(_ operation: () throws -> ()) rethrows { | |
let start = Date() | |
try operation() | |
let end = Date().timeIntervalSince(start) | |
print(end) | |
} | |
let sourceURL = URL(fileURLWithPath: CommandLine.arguments[1]) | |
let destinationURL = URL(fileURLWithPath: CommandLine.arguments[2]) | |
do { | |
try time { | |
let asset = AVURLAsset(url: sourceURL) | |
let trimmedAsset = try asset.assetByTrimming(timeOffStart: 1.0) | |
try trimmedAsset.export(to: destinationURL) | |
} | |
} catch let error { | |
print("💩 \(error)") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @jakebromberg, thanks for the snippets!
assetByTrimming
turns vertical videos into horizontal videos. Do you know why?