Flesh out documentation for most of the public API

This commit is contained in:
Sami Samhuri 2024-08-18 13:08:45 -07:00
parent 16599d638c
commit b60032f15f
No known key found for this signature in database
9 changed files with 128 additions and 39 deletions

View file

@ -1,25 +1,31 @@
# Overview
# 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
## 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
### Supported Platforms
This package is supported on iOS 17.0+, macOS Sonoma 14.0+, and visionOS 1.3+.
## Xcode
### 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)
### Swift Package Manager (SPM)
When you're integrating this using SPM on its own then add this to your Package.swift file:
@ -27,11 +33,11 @@ When you're integrating this using SPM on its own then add this to your Package.
.package(url: "https://github.com/samsonjs/SJSAssetExportSession.git", .upToNextMajor(from: "1.0"))
```
# Usage
## 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
### The Nice Way
This should be fairly self-explanatory:
@ -58,7 +64,7 @@ try await exporter.export(
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
### All Nice Parameters
Here are all of the parameters you can pass into the nice export method:
@ -94,7 +100,7 @@ try await exporter.export(
)
```
## The Most Flexible Way
### 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:
@ -138,7 +144,7 @@ try await exporter.export(
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoAverageBitRateKey: NSNumber(value: 1_000_000),
] as [String: (any Sendable)],
] as [String: any Sendable],
AVVideoColorPropertiesKey: [
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,
@ -151,13 +157,9 @@ try await exporter.export(
)
```
It's an effective illustration of why the nicer API exists right? 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. But when you need this flexibility then it's available for you.
It's an effective illustration of why the nicer API exists right? But when you need this flexibility then it's available for you.
[audio settings]: https://developer.apple.com/documentation/avfoundation/audio_settings
[video settings]: https://developer.apple.com/documentation/avfoundation/video_settings
## Mix and Match
### Mix and Match
`AudioOutputSettings` and `VideoOutputSettings` have a property named `settingsDictionary` and you can use that to bootstrap your own custom settings.
@ -188,7 +190,7 @@ try await exporter.export(
)
```
# License
## License
Copyright © 2024 Sami Samhuri, https://samhuri.net <sami@samhuri.net>. Released under the terms of the [MIT License][MIT].

View file

@ -7,9 +7,15 @@
public import AVFoundation
/// A convenient API for constructing audio settings dictionaries.
///
/// Construct this by starting with ``AudioOutputSettings/default`` or ``AudioOutputSettings/format(_:)`` and then chain calls to further customize it, if desired, using ``channels(_:)``, ``sampleRate(_:)``, and ``mix(_:)``.
public struct AudioOutputSettings {
/// Describes the output file format.
public enum Format {
/// Advanced Audio Codec. The audio format typically used for MPEG-4 audio.
case aac
/// The MPEG Layer 3 audio format.
case mp3
var formatID: AudioFormatID {
@ -25,10 +31,12 @@ public struct AudioOutputSettings {
let sampleRate: Int?
let mix: AVAudioMix?
/// Specifies the AAC format with 2 channels at a 44.1 KHz sample rate.
public static var `default`: AudioOutputSettings {
.format(.aac).channels(2).sampleRate(44_100)
}
/// Specifies the given format with 2 channels.
public static func format(_ format: Format) -> AudioOutputSettings {
.init(format: format.formatID, channels: 2, sampleRate: nil, mix: nil)
}
@ -45,7 +53,7 @@ public struct AudioOutputSettings {
.init(format: format, channels: channels, sampleRate: sampleRate, mix: mix)
}
var settingsDictionary: [String: any Sendable] {
public var settingsDictionary: [String: any Sendable] {
if let sampleRate {
[
AVFormatIDKey: format,

View file

@ -16,6 +16,28 @@ public final class ExportSession: Sendable {
(progressStream, progressContinuation) = AsyncStream<Float>.makeStream()
}
/**
Exports the given asset using all of the other parameters to transform it in some way. This method uses code to build up audio and video settings with a nice API instead of diving into the nitty gritty settings dictionaries. Monitor progress using ``progressStream``.
- Parameters:
- asset: The source asset to export. This can be any kind of `AVAsset` including subclasses such as `AVComposition`.
- optimizeForNetworkUse: Setting this value to `true` writes the output file in a form that enables a player to begin playing the media after downloading only a small portion of it. Defaults to `false`.
- metadata: Optional array of `AVMetadataItem`s to be written out with the exported asset.
- timeRange: Providing a time range exports a subset of the asset instead of the entire duration, which is the default behaviour.
- audio: Optional audio settings using ``AudioOutputSettings``. Defaults to ``AudioOutputSettings/default``.
- video: Video settings using ``VideoOutputSettings``.
- outputURL: The file `URL` where the exported video will be written.
- fileType: The type of of video file to export. This will typically be one of `AVFileType.mp4`, `AVFileType.m4v`, or `AVFileType.mov`.
- Throws: One of the cases in the ``ExportSession/Error`` enum when the export fails. See ``ExportSession/Error`` for possible failures.
*/
public func export(
asset: sending AVAsset,
optimizeForNetworkUse: Bool = false,
@ -50,7 +72,7 @@ public final class ExportSession: Sendable {
}
/**
Exports the given asset using all of the other parameters to transform it in some way.
Exports the given asset using all of the other parameters to transform it in some way. This method provides the most control over the export using audio and video settings dictionaries, in addition to an optionial audio mix and optional video composition. Monitor progress using ``progressStream``.
- Parameters:
- asset: The source asset to export. This can be any kind of `AVAsset` including subclasses such as `AVComposition`.
@ -77,15 +99,17 @@ public final class ExportSession: Sendable {
- outputURL: The file URL where the exported video will be written.
- fileType: The type of of video file to export. This will typically be one of `AVFileType.mp4`, `AVFileType.m4v`, or `AVFileType.mov`.
- Throws: One of the cases in the ``ExportSession/Error`` enum when the export fails. See ``ExportSession/Error`` for possible failures.
*/
public func export(
asset: sending AVAsset,
optimizeForNetworkUse: Bool = false,
metadata: sending [AVMetadataItem] = [],
timeRange: CMTimeRange? = nil,
audioOutputSettings: [String: (any Sendable)],
audioOutputSettings: [String: any Sendable],
mix: sending AVAudioMix? = nil,
videoOutputSettings: [String: (any Sendable)],
videoOutputSettings: [String: any Sendable],
composition: sending AVVideoComposition? = nil,
to outputURL: URL,
as fileType: AVFileType

View file

@ -1,13 +1,49 @@
# ``SJSAssetExportSession``
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
`SJSAssetExportSession` is an alternative to `AVAssetExportSession` that lets you provide custom audio and video settings, without dropping down into the world of `AVAssetReader` and `AVAssetWriter`.
## Overview
[`AVAssetExportSession`][AV] is fine for some things but it provides basically no way to customize the export settings, besides the couple of options on `AVVideoComposition` like render size and frame rate. This package has similar capabilites to the venerable [`SDAVAssetExportSession`][SDAV] but the API is completely different, the code is written in Swift, and it's ready for the world of strict concurrency.
<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->
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
The simplest usage is something like this:
```swift
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Progress: \(progress)")
}
}
try await exporter.export(
asset: AVURLAsset(url: sourceURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true]),
video: .codec(.h264, width: 1280, height: 720),
to: URL.temporaryDirectory.appeding(component: "new-video.mp4"),
as: .mp4
)
```
## Topics
### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
### Exporting
- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
- ``ExportSession``
- ``ExportSession/Error``
- ``ExportSession/SetupFailureReason``
### Audio Output Settings
- ``AudioOutputSettings``
- ``AudioOutputSettings/Format``
### Video Output Settings
- ``VideoOutputSettings``
- ``VideoOutputSettings/Codec``
- ``VideoOutputSettings/H264Profile``
- ``VideoOutputSettings/Color``

View file

@ -32,9 +32,9 @@ actor SampleWriter {
}
private var progressContinuation: AsyncStream<Float>.Continuation?
private let audioOutputSettings: [String: (any Sendable)]
private let audioOutputSettings: [String: any Sendable]
private let audioMix: AVAudioMix?
private let videoOutputSettings: [String: (any Sendable)]
private let videoOutputSettings: [String: any Sendable]
private let videoComposition: AVVideoComposition?
private let reader: AVAssetReader
private let writer: AVAssetWriter
@ -48,9 +48,9 @@ actor SampleWriter {
nonisolated init(
asset: sending AVAsset,
audioOutputSettings: sending [String: (any Sendable)],
audioOutputSettings: sending [String: any Sendable],
audioMix: sending AVAudioMix?,
videoOutputSettings: sending [String: (any Sendable)],
videoOutputSettings: sending [String: any Sendable],
videoComposition: sending AVVideoComposition,
timeRange: CMTimeRange? = nil,
optimizeForNetworkUse: Bool = false,

View file

@ -7,7 +7,13 @@
import AVFoundation
/// A convenient API for constructing video settings dictionaries.
///
/// Construct this by starting with ``VideoOutputSettings/codec(_:size:)`` or ``VideoOutputSettings/codec(_:width:height:)`` and then chaining calls to further customize it, if desired, using ``fps(_:)``, ``bitrate(_:)``, and ``color(_:)``.
///
/// Setting the fps and colour also needs support from the `AVVideoComposition` and these settings can be applied to them with ``VideoOutputSettings/apply(to:)``.
public struct VideoOutputSettings {
/// Describes an H.264 encoding profile.
public enum H264Profile {
case baselineAuto, baseline30, baseline31, baseline41
case mainAuto, main31, main32, main41
@ -30,11 +36,15 @@ public struct VideoOutputSettings {
}
}
/// Specifies the output codec.
public enum Codec {
/// H.264 using the associated encoding profile.
case h264(H264Profile)
/// HEVC / H.265
case hevc
static var h264: Codec {
/// Construct Codec.h264 using the default profile `H264Profile.highAuto`.
public static var h264: Codec {
.h264(.highAuto)
}
@ -53,8 +63,12 @@ public struct VideoOutputSettings {
}
}
/// Specifies whether to use Standard Dynamic Range or High Dynamic Range colours.
public enum Color {
case sdr, hdr
/// Standard dynamic range colours (BT.709 which roughly corresponds to SRGB)
case sdr
/// High dynamic range colours (BT.2020)
case hdr
var properties: [String: any Sendable] {
switch self {
@ -100,7 +114,7 @@ public struct VideoOutputSettings {
.init(codec: codec, size: size, fps: fps, bitrate: bitrate, color: color)
}
var settingsDictionary: [String: any Sendable] {
public var settingsDictionary: [String: any Sendable] {
var result: [String: any Sendable] = [
AVVideoCodecKey: codec.stringValue,
AVVideoWidthKey: NSNumber(value: Int(size.width)),
@ -121,6 +135,11 @@ public struct VideoOutputSettings {
}
return result
}
/// Applies the subset of relevant settings to the given video composition, namely fps and colour.
public func apply(to videoComposition: AVMutableVideoComposition) {
_ = videoComposition.applyingSettings(self)
}
}
extension AVMutableVideoComposition {

View file

@ -5,7 +5,7 @@
// Created by Sami Samhuri on 2024-07-07.
//
internal import AVFoundation
import AVFoundation
extension AVAsset {
func sendTracks(withMediaType mediaType: AVMediaType) async throws -> sending [AVAssetTrack] {

View file

@ -5,8 +5,8 @@
// Created by Sami Samhuri on 2024-08-18.
//
internal import AVFoundation
@testable import SJSAssetExportSession
import AVFoundation
import SJSAssetExportSession
private func readmeNiceExample() async throws {
let sourceURL = URL.documentsDirectory.appending(component: "some-video.mov")
@ -101,7 +101,7 @@ private func readmeFlexibleExample() async throws {
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoAverageBitRateKey: NSNumber(value: 1_000_000),
] as [String: (any Sendable)],
] as [String: any Sendable],
AVVideoColorPropertiesKey: [
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,

View file

@ -5,9 +5,9 @@
// Created by Sami Samhuri on 2024-06-29.
//
internal import AVFoundation
import AVFoundation
import CoreLocation
@testable import SJSAssetExportSession
import SJSAssetExportSession
import Testing
final class ExportSessionTests {