SJSAssetExportSession/Readme.md

199 lines
7.6 KiB
Markdown

# SJSAssetExportSession
## Overview
`SJSAssetExportSession` is an alternative to [`AVAssetExportSession`][AV] that lets you provide custom audio and video settings, without dropping down into the world of `AVAssetReader` and `AVAssetWriter`. It has similar capabilites to [SDAVAssetExportSession][SDAV] but the API is completely different, the code is written in Swift, and it's ready for the world of strict concurrency.
You shouldn't have to read through [audio settings][] and [video settings][] just to set the bitrate, and setting the frame rate can be tricky, so there's a nicer API that builds these settings dictionaries with some commonly used settings.
[AV]: https://developer.apple.com/documentation/avfoundation/avassetexportsession
[SDAV]: https://github.com/rs/SDAVAssetExportSession
[audio settings]: https://developer.apple.com/documentation/avfoundation/audio_settings
[video settings]: https://developer.apple.com/documentation/avfoundation/video_settings
## Installation
The only way to install this package is with Swift Package Manager (SPM). Please [file a new issue][] or submit a pull-request if you want to use something else.
[file a new issue]: https://github.com/samsonjs/SJSAssetExportSession/issues/new
### Supported Platforms
This package is supported on iOS 17.0+, macOS Sonoma 14.0+, and visionOS 1.3+.
### Xcode
When you're integrating this into an app with Xcode then go to your project's Package Dependencies and enter the URL `https://github.com/samsonjs/SJSAssetExportSession` and then go through the usual flow for adding packages.
### Swift Package Manager (SPM)
When you're integrating this using SPM on its own then add this to the list of dependencies your Package.swift file:
```swift
.package(url: "https://github.com/samsonjs/SJSAssetExportSession.git", .upToNextMajor(from: "0.3.0"))
```
and then add `"SJSAssetExportSession"` to the list of dependencies in your target as well.
## Usage
There are two ways of exporting assets: one using dictionaries for audio and video settings just like with `SDAVAssetExportSession`, and the other using a builder-like API with data structures for commonly used settings.
### The Nice Way
This should be fairly self-explanatory:
```swift
let sourceURL = URL.documentsDirectory.appending(component: "some-video.mov")
let sourceAsset = AVURLAsset(url: sourceURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: true,
])
let destinationURL = URL.temporaryDirectory.appending(component: "shiny-new-video.mp4")
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Export progress: \(progress)")
}
}
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
Most of the audio and video configuration is optional which is why there are no audio settings specified here. By default you get AAC with 2 channels at a 44.1 KHz sample rate.
### All Nice Parameters
Here are all of the parameters you can pass into the nice export method:
```swift
let sourceURL = URL.documentsDirectory.appending(component: "some-video.mov")
let sourceAsset = AVURLAsset(url: sourceURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: true,
])
let destinationURL = URL.temporaryDirectory.appending(component: "shiny-new-video.mp4")
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Export progress: \(progress)")
}
}
let locationMetadata = AVMutableMetadataItem()
locationMetadata.key = AVMetadataKey.commonKeyLocation.rawValue as NSString
locationMetadata.keySpace = .common
locationMetadata.value = "+48.50176+123.34368/" as NSString
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
metadata: [locationMetadata],
timeRange: CMTimeRange(start: .zero, duration: .seconds(1)),
audio: .format(.mp3).channels(1).sampleRate(22_050),
video: .codec(.h264, width: 1280, height: 720)
.fps(24)
.bitrate(1_000_000)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
### The Most Flexible Way
When you need all the control you can get down to the nitty gritty details. This code does the exact same thing as the code above:
```swift
let sourceURL = URL.documentsDirectory.appending(component: "some-video.mov")
let sourceAsset = AVURLAsset(url: sourceURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: true,
])
let destinationURL = URL.temporaryDirectory.appending(component: "shiny-new-video.mp4")
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Export progress: \(progress)")
}
}
let locationMetadata = AVMutableMetadataItem()
locationMetadata.key = AVMetadataKey.commonKeyLocation.rawValue as NSString
locationMetadata.keySpace = .common
locationMetadata.value = "+48.50176+123.34368/" as NSString
let videoComposition = try await AVMutableVideoComposition.videoComposition(withPropertiesOf: sourceAsset)
videoComposition.renderSize = CGSize(width: 1280, height: 720)
videoComposition.sourceTrackIDForFrameTiming = kCMPersistentTrackID_Invalid
videoComposition.frameDuration = CMTime(value: 600 / 24, timescale: 600) // 24 fps
videoComposition.colorPrimaries = AVVideoColorPrimaries_ITU_R_709_2
videoComposition.colorTransferFunction = AVVideoTransferFunction_ITU_R_709_2
videoComposition.colorYCbCrMatrix = AVVideoYCbCrMatrix_ITU_R_709_2
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
metadata: [locationMetadata],
timeRange: CMTimeRange(start: .zero, duration: .seconds(1)),
audioOutputSettings: [
AVFormatIDKey: kAudioFormatMPEGLayer3,
AVNumberOfChannelsKey: NSNumber(value: 1),
AVSampleRateKey: NSNumber(value: 22_050),
],
videoOutputSettings: [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoAverageBitRateKey: NSNumber(value: 1_000_000),
] as [String: any Sendable],
AVVideoColorPropertiesKey: [
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,
AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709_2,
],
],
composition: videoComposition,
to: destinationURL,
as: .mp4
)
```
It's an effective illustration of why the nicer API exists right? But when you need this flexibility then it's available for you.
### Mix and Match
`AudioOutputSettings` and `VideoOutputSettings` have a property named `settingsDictionary` and you can use that to bootstrap your own custom settings.
```swift
let sourceURL = URL.documentsDirectory.appending(component: "some-video.mov")
let sourceAsset = AVURLAsset(url: sourceURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: true,
])
let destinationURL = URL.temporaryDirectory.appending(component: "shiny-new-video.mp4")
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Export progress: \(progress)")
}
}
var audioSettings = AudioOutputSettings.default.settingsDictionary
audioSettings[AVVideoAverageBitRateKey] = 65_536
let videoSettings = VideoOutputSettings
.codec(.hevc, width: 1280, height: 720)
.settingsDictionary
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: audioSettings,
videoOutputSettings: videoSettings,
to: destinationURL,
as: .mp4
)
```
## License
Copyright © 2024 Sami Samhuri, https://samhuri.net <sami@samhuri.net>. Released under the terms of the [MIT License][MIT].
[MIT]: https://sjs.mit-license.org