Skip to content

Instantly share code, notes, and snippets.

@jakebromberg
Forked from acj/TrimVideo.swift
Last active August 16, 2024 15:41
Show Gist options
  • Save jakebromberg/098c328d87bd25ec0ae693b877cb933c to your computer and use it in GitHub Desktop.
Save jakebromberg/098c328d87bd25ec0ae693b877cb933c to your computer and use it in GitHub Desktop.
Trim video using AVFoundation in Swift
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)")
}
@buhussain
Copy link

guys am trying to write a function that can take a video URL and return the same video chopped in pieces each piece 10 sec long, any idea how to do it?

thanx

@sercanorhangazi
Copy link

Hey @jakebromberg, thanks for the snippets!
assetByTrimming turns vertical videos into horizontal videos. Do you know why?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment