WIP: DocC

This commit is contained in:
Sami Samhuri 2025-06-06 15:18:19 -07:00
parent 0eefb949e2
commit 462694bb85
No known key found for this signature in database
9 changed files with 3338 additions and 15 deletions

View file

@ -0,0 +1,326 @@
# Audio Configuration
Learn how to configure audio settings for your video exports.
## Overview
SJSAssetExportSession provides flexible audio configuration through the ``AudioOutputSettings`` builder pattern. You can easily specify format, channels, sample rate, and more advanced options.
## Basic Audio Settings
### Default AAC Configuration
The simplest approach uses the default AAC settings:
```swift
try await exporter.export(
asset: sourceAsset,
audio: .default, // AAC, 2 channels, 44.1 kHz
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
The default configuration provides:
- Format: AAC (kAudioFormatMPEG4AAC)
- Channels: 2 (stereo)
- Sample Rate: 44,100 Hz
### Specifying Audio Format
Choose between supported audio formats:
```swift
// AAC format (recommended for MP4)
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
// MP3 format
try await exporter.export(
asset: sourceAsset,
audio: .format(.mp3),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
## Channel Configuration
### Mono Audio
For voice recordings or to reduce file size:
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(1),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
### Stereo Audio
Standard stereo configuration:
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(2),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
### Multi-Channel Audio
For surround sound or complex audio setups:
```swift
// 5.1 surround sound
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(6),
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mov
)
```
## Sample Rate Configuration
### Common Sample Rates
Choose the appropriate sample rate for your content:
```swift
// CD quality (44.1 kHz)
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).sampleRate(44_100),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
// Professional audio (48 kHz)
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).sampleRate(48_000),
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mov
)
// High-resolution audio (96 kHz)
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).sampleRate(96_000),
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mov
)
```
### Reduced Quality for Web
For web streaming or mobile apps:
```swift
// Lower quality for smaller file size
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(1).sampleRate(22_050),
video: .codec(.h264, width: 854, height: 480),
to: destinationURL,
as: .mp4
)
```
## Audio Format Guidelines
### AAC Format
Best for:
- MP4/MOV containers
- Streaming applications
- Mobile devices
- General compatibility
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(2).sampleRate(48_000),
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
### MP3 Format
Best for:
- Legacy compatibility
- Audio-focused applications
- When file size is critical
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.mp3).channels(2).sampleRate(44_100),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
## Builder Pattern Chaining
Combine multiple audio settings using method chaining:
```swift
// Complete audio configuration
let audioSettings = AudioOutputSettings
.format(.aac)
.channels(2)
.sampleRate(48_000)
try await exporter.export(
asset: sourceAsset,
audio: audioSettings,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
## Audio Mix Integration
Combine audio settings with audio mix for advanced processing:
```swift
// Create an audio mix for volume control
let audioMix = AVMutableAudioMix()
let inputParameters = AVMutableAudioMixInputParameters(track: audioTrack)
inputParameters.setVolume(0.5, at: .zero) // 50% volume
audioMix.inputParameters = [inputParameters]
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(2).sampleRate(48_000),
mix: audioMix,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
## Audio-Only Exports
Export audio without video:
```swift
// Note: You still need to provide video settings, but the output will be audio-only
// if the source asset has no video tracks
try await exporter.export(
asset: audioOnlyAsset,
audio: .format(.aac).channels(2).sampleRate(44_100),
video: .codec(.h264, width: 1, height: 1), // Minimal video settings
to: destinationURL,
as: .m4a
)
```
## Common Audio Configurations
### Podcast Export
Optimized for speech content:
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(1).sampleRate(22_050),
video: .codec(.h264, width: 640, height: 360),
to: destinationURL,
as: .mp4
)
```
### Music Video Export
High-quality audio for music content:
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(2).sampleRate(48_000),
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
### Social Media Export
Balanced quality for social platforms:
```swift
try await exporter.export(
asset: sourceAsset,
audio: .format(.aac).channels(2).sampleRate(44_100),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
## Troubleshooting Audio Issues
### No Audio in Output
If your exported video has no audio:
1. Check that the source asset has audio tracks
2. Ensure audio settings are properly configured
3. Verify the output container supports your audio format
```swift
// Check for audio tracks
let audioTracks = try await sourceAsset.loadTracks(withMediaType: .audio)
if audioTracks.isEmpty {
print("Source asset has no audio tracks")
}
```
### Audio Quality Issues
For better audio quality:
- Use higher sample rates (48 kHz or higher)
- Choose AAC over MP3 when possible
- Ensure sufficient bitrate for your channel configuration
### Compatibility Issues
For maximum compatibility:
- Use AAC format with MP4 container
- Stick to standard sample rates (44.1 kHz, 48 kHz)
- Use stereo (2 channels) for general content
## See Also
- ``AudioOutputSettings`` - Audio settings builder
- ``AudioOutputSettings/Format`` - Supported audio formats
- <doc:VideoConfiguration> - Configuring video settings
- <doc:CustomSettings> - Using raw audio settings dictionaries

View file

@ -0,0 +1,374 @@
# Custom Settings
Learn how to use raw settings dictionaries for maximum control over export parameters.
## Overview
While SJSAssetExportSession provides convenient builder patterns through ``AudioOutputSettings`` and ``VideoOutputSettings``, you can also use raw settings dictionaries for complete control over export parameters. This approach gives you access to every AVFoundation setting while maintaining the benefits of the export session's architecture.
## Raw Settings API
### Using Raw Settings Dictionaries
The flexible export method accepts raw settings dictionaries:
```swift
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: audioSettingsDict,
videoOutputSettings: videoSettingsDict,
to: destinationURL,
as: .mp4
)
```
This method provides the most control over the export process and allows you to specify any settings supported by AVFoundation.
## Audio Settings Dictionaries
### Basic Audio Settings
```swift
let audioSettings: [String: any Sendable] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey: NSNumber(value: 2),
AVSampleRateKey: NSNumber(value: 48_000)
]
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: audioSettings,
videoOutputSettings: videoSettings,
to: destinationURL,
as: .mp4
)
```
### Advanced Audio Settings
```swift
let advancedAudioSettings: [String: any Sendable] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey: NSNumber(value: 2),
AVSampleRateKey: NSNumber(value: 48_000),
AVEncoderBitRateKey: NSNumber(value: 128_000), // 128 kbps
AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.high.rawValue),
AVChannelLayoutKey: AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_Stereo)!.asData()
]
```
### Multi-Channel Audio
For surround sound configurations:
```swift
let surroundAudioSettings: [String: any Sendable] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVChannelLayoutKey: AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_5_1)!.asData(),
AVSampleRateKey: NSNumber(value: 48_000),
AVEncoderBitRateKey: NSNumber(value: 384_000) // Higher bitrate for 5.1
]
```
## Video Settings Dictionaries
### Basic Video Settings
```swift
let videoSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1920),
AVVideoHeightKey: NSNumber(value: 1080)
]
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: audioSettings,
videoOutputSettings: videoSettings,
to: destinationURL,
as: .mp4
)
```
### Advanced Video Settings
```swift
let advancedVideoSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1920),
AVVideoHeightKey: NSNumber(value: 1080),
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoAverageBitRateKey: NSNumber(value: 5_000_000),
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 30),
AVVideoAllowFrameReorderingKey: NSNumber(value: true),
AVVideoExpectedSourceFrameRateKey: NSNumber(value: 30),
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
] as [String: any Sendable],
AVVideoColorPropertiesKey: [
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,
AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709_2
]
]
```
### HEVC Settings
```swift
let hevcSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.hevc.rawValue,
AVVideoWidthKey: NSNumber(value: 3840),
AVVideoHeightKey: NSNumber(value: 2160),
AVVideoCompressionPropertiesKey: [
AVVideoAverageBitRateKey: NSNumber(value: 20_000_000),
AVVideoQualityKey: NSNumber(value: 0.8),
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 60)
] as [String: any Sendable],
AVVideoColorPropertiesKey: [
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_2020,
AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_2100_HLG,
AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_2020
]
]
```
## Mix-and-Match Approach
### Bootstrap from Builder Patterns
Start with builder patterns and customize as needed:
```swift
// Start with builder pattern
var audioSettings = AudioOutputSettings
.format(.aac)
.channels(2)
.sampleRate(48_000)
.settingsDictionary
// Add custom settings
audioSettings[AVEncoderBitRateKey] = NSNumber(value: 192_000)
audioSettings[AVEncoderAudioQualityKey] = NSNumber(value: AVAudioQuality.max.rawValue)
var videoSettings = VideoOutputSettings
.codec(.h264, width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000)
.settingsDictionary
// Add advanced H.264 settings
if var compressionProps = videoSettings[AVVideoCompressionPropertiesKey] as? [String: any Sendable] {
compressionProps[AVVideoH264EntropyModeKey] = AVVideoH264EntropyModeCABAC
compressionProps[AVVideoAllowFrameReorderingKey] = NSNumber(value: true)
videoSettings[AVVideoCompressionPropertiesKey] = compressionProps
}
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: audioSettings,
videoOutputSettings: videoSettings,
to: destinationURL,
as: .mp4
)
```
## Video Composition Integration
### Custom Video Composition
When using raw settings, you can provide your own video composition:
```swift
let videoComposition = try await AVMutableVideoComposition.videoComposition(withPropertiesOf: sourceAsset)
videoComposition.renderSize = CGSize(width: 1920, height: 1080)
videoComposition.frameDuration = CMTime(value: 1, timescale: 30) // 30 fps
// Add filters or effects
let instruction = videoComposition.instructions.first as? AVMutableVideoCompositionInstruction
let layerInstruction = instruction?.layerInstructions.first as? AVMutableVideoCompositionLayerInstruction
// Apply transform or filters here...
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: audioSettings,
videoOutputSettings: videoSettings,
composition: videoComposition,
to: destinationURL,
as: .mp4
)
```
## Advanced Use Cases
### Variable Bitrate Encoding
```swift
let vbrVideoSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1920),
AVVideoHeightKey: NSNumber(value: 1080),
AVVideoCompressionPropertiesKey: [
AVVideoQualityKey: NSNumber(value: 0.7), // Use quality instead of bitrate
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 60),
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel
] as [String: any Sendable]
]
```
### Custom Audio Channel Layout
```swift
// Create custom channel layout for 7.1 surround
let channelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_7_1)!
let customAudioSettings: [String: any Sendable] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVChannelLayoutKey: channelLayout.asData(),
AVSampleRateKey: NSNumber(value: 48_000),
AVEncoderBitRateKey: NSNumber(value: 512_000) // Higher bitrate for 7.1
]
```
### Low-Latency Encoding
```swift
let lowLatencySettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1280),
AVVideoHeightKey: NSNumber(value: 720),
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel,
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 1), // I-frame only
AVVideoAllowFrameReorderingKey: NSNumber(value: false),
AVVideoRealTimeKey: NSNumber(value: true)
] as [String: any Sendable]
]
```
## Settings Validation
### Validating Custom Settings
```swift
func validateSettings(
audioSettings: [String: any Sendable],
videoSettings: [String: any Sendable],
fileType: AVFileType
) throws {
// Create temporary writer to validate settings
let tempURL = URL.temporaryDirectory.appending(component: UUID().uuidString)
let writer = try AVAssetWriter(outputURL: tempURL, fileType: fileType)
// Validate audio settings if provided
if !audioSettings.isEmpty {
guard writer.canApply(outputSettings: audioSettings, forMediaType: .audio) else {
throw ExportSession.Error.setupFailure(.audioSettingsInvalid)
}
}
// Validate video settings
guard writer.canApply(outputSettings: videoSettings, forMediaType: .video) else {
throw ExportSession.Error.setupFailure(.videoSettingsInvalid)
}
// Clean up
try? FileManager.default.removeItem(at: tempURL)
}
```
## Common Settings References
### H.264 Compression Properties
| Key | Type | Description |
|-----|------|-------------|
| `AVVideoAverageBitRateKey` | NSNumber | Average bitrate in bits per second |
| `AVVideoQualityKey` | NSNumber | Quality factor (0.0-1.0) |
| `AVVideoMaxKeyFrameIntervalKey` | NSNumber | Maximum keyframe interval |
| `AVVideoProfileLevelKey` | String | H.264 profile and level |
| `AVVideoAllowFrameReorderingKey` | NSNumber (Bool) | Enable B-frames |
| `AVVideoH264EntropyModeKey` | String | CAVLC or CABAC entropy mode |
### Audio Format Properties
| Key | Type | Description |
|-----|------|-------------|
| `AVFormatIDKey` | AudioFormatID | Audio codec identifier |
| `AVSampleRateKey` | NSNumber | Sample rate in Hz |
| `AVNumberOfChannelsKey` | NSNumber | Number of audio channels |
| `AVChannelLayoutKey` | Data | Channel layout information |
| `AVEncoderBitRateKey` | NSNumber | Audio bitrate in bits per second |
| `AVEncoderAudioQualityKey` | NSNumber | Audio quality setting |
## Performance Considerations
### Optimizing Custom Settings
```swift
// For fast encoding (lower quality)
let fastVideoSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1280),
AVVideoHeightKey: NSNumber(value: 720),
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel,
AVVideoAverageBitRateKey: NSNumber(value: 2_000_000),
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 12),
AVVideoAllowFrameReorderingKey: NSNumber(value: false)
] as [String: any Sendable]
]
// For high quality (slower encoding)
let qualityVideoSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1920),
AVVideoHeightKey: NSNumber(value: 1080),
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
AVVideoQualityKey: NSNumber(value: 0.9),
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 30),
AVVideoAllowFrameReorderingKey: NSNumber(value: true)
] as [String: any Sendable]
]
```
## Troubleshooting Custom Settings
### Common Issues
1. **Invalid Settings**: Always validate settings with `AVAssetWriter.canApply(outputSettings:forMediaType:)`
2. **Missing Required Keys**: Ensure `AVFormatIDKey` for audio and `AVVideoCodecKey` for video
3. **Type Mismatches**: Use `NSNumber` for numeric values, not Swift native types
4. **Channel Layout Data**: Convert `AVAudioChannelLayout` to `Data` using `asData()`
### Debug Settings
```swift
func debugSettings(_ settings: [String: any Sendable], mediaType: AVMediaType) {
print("Settings for \(mediaType.rawValue):")
for (key, value) in settings {
print(" \(key): \(value)")
}
// Test with a temporary writer
do {
let tempURL = URL.temporaryDirectory.appending(component: "test")
let writer = try AVAssetWriter(outputURL: tempURL, fileType: .mp4)
let canApply = writer.canApply(outputSettings: settings, forMediaType: mediaType)
print(" Can apply: \(canApply)")
try? FileManager.default.removeItem(at: tempURL)
} catch {
print(" Validation error: \(error)")
}
}
```
## See Also
- ``AudioOutputSettings`` - Audio settings builder
- ``VideoOutputSettings`` - Video settings builder
- <doc:AudioConfiguration> - Builder pattern for audio
- <doc:VideoConfiguration> - Builder pattern for video
- <doc:ErrorHandling> - Handling settings validation errors

View file

@ -0,0 +1,446 @@
# Error Handling
Learn how to properly handle errors and troubleshoot common issues with SJSAssetExportSession.
## Overview
SJSAssetExportSession provides comprehensive error reporting through the ``ExportSession/Error`` enum. This guide covers how to handle different types of errors and recover from common failure scenarios.
## Error Types
### ExportSession.Error
The main error type with three categories:
```swift
public enum Error: LocalizedError, Equatable {
case setupFailure(SetupFailureReason)
case readFailure((any Swift.Error)?)
case writeFailure((any Swift.Error)?)
}
```
## Setup Failures
Setup failures occur during export initialization, before any media processing begins.
### Common Setup Failures
```swift
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
} catch ExportSession.Error.setupFailure(let reason) {
switch reason {
case .videoTracksEmpty:
print("Source asset has no video tracks")
case .audioSettingsEmpty:
print("Audio settings dictionary is empty")
case .audioSettingsInvalid:
print("Audio settings are not valid for this format")
case .videoSettingsInvalid:
print("Video settings are not valid for this format")
case .cannotAddAudioInput:
print("Cannot add audio input to the writer")
case .cannotAddAudioOutput:
print("Cannot add audio output to the reader")
case .cannotAddVideoInput:
print("Cannot add video input to the writer")
case .cannotAddVideoOutput:
print("Cannot add video output to the reader")
}
}
```
### Handling Setup Failures
#### No Video Tracks
```swift
// Check for video tracks before export
let videoTracks = try await sourceAsset.loadTracks(withMediaType: .video)
guard !videoTracks.isEmpty else {
print("Asset has no video tracks - cannot export")
return
}
```
#### Invalid Settings
```swift
// Validate settings compatibility
do {
let writer = try AVAssetWriter(outputURL: tempURL, fileType: .mp4)
let videoSettings = VideoOutputSettings.codec(.h264, width: 1920, height: 1080).settingsDictionary
guard writer.canApply(outputSettings: videoSettings, forMediaType: .video) else {
print("Video settings are not compatible with MP4 format")
return
}
} catch {
print("Failed to create writer: \(error)")
}
```
## Read Failures
Read failures occur when the asset reader encounters problems reading the source media.
### Handling Read Failures
```swift
do {
try await exporter.export(/* ... */)
} catch ExportSession.Error.readFailure(let underlyingError) {
if let error = underlyingError {
print("Read failed: \(error.localizedDescription)")
// Handle specific AVFoundation errors
if let avError = error as? AVError {
switch avError.code {
case .fileFormatNotRecognized:
print("File format not supported")
case .mediaServicesWereReset:
print("Media services were reset - retry may succeed")
case .diskFull:
print("Not enough disk space")
default:
print("AVFoundation error: \(avError.localizedDescription)")
}
}
} else {
print("Unknown read failure")
}
}
```
### Common Read Failure Causes
- Corrupted source files
- Unsupported file formats
- Permission issues
- Network interruption (for remote assets)
- Media services restart
## Write Failures
Write failures occur when the asset writer cannot write to the destination.
### Handling Write Failures
```swift
do {
try await exporter.export(/* ... */)
} catch ExportSession.Error.writeFailure(let underlyingError) {
if let error = underlyingError {
print("Write failed: \(error.localizedDescription)")
if let avError = error as? AVError {
switch avError.code {
case .diskFull:
print("Not enough disk space for export")
case .fileAlreadyExists:
print("Destination file already exists")
case .noPermission:
print("No permission to write to destination")
default:
print("Write error: \(avError.localizedDescription)")
}
}
} else {
print("Unknown write failure")
}
}
```
### Common Write Failure Causes
- Insufficient disk space
- File permissions
- Destination file already exists
- Invalid destination path
- Unsupported format combination
## Comprehensive Error Handling
### Complete Error Handling Pattern
```swift
func exportVideoWithErrorHandling() async {
do {
let exporter = ExportSession()
// Optional: Monitor progress
Task {
for await progress in exporter.progressStream {
print("Progress: \(Int(progress * 100))%")
}
}
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
print("Export completed successfully!")
} catch let error as ExportSession.Error {
handleExportError(error)
} catch {
print("Unexpected error: \(error.localizedDescription)")
}
}
private func handleExportError(_ error: ExportSession.Error) {
switch error {
case .setupFailure(let reason):
print("Setup failed: \(reason.description)")
// Could show user-friendly message based on reason
case .readFailure(let underlyingError):
print("Failed to read source: \(underlyingError?.localizedDescription ?? "Unknown")")
// Could suggest checking source file
case .writeFailure(let underlyingError):
print("Failed to write output: \(underlyingError?.localizedDescription ?? "Unknown")")
// Could suggest checking disk space or permissions
}
}
```
## Retry Strategies
### Automatic Retry with Backoff
```swift
func exportWithRetry(maxAttempts: Int = 3) async throws {
var lastError: Error?
for attempt in 1...maxAttempts {
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
return // Success!
} catch let error as ExportSession.Error {
lastError = error
// Only retry for certain types of failures
switch error {
case .readFailure(let underlyingError):
if let avError = underlyingError as? AVError,
avError.code == .mediaServicesWereReset {
print("Media services reset, retrying... (attempt \(attempt))")
try await Task.sleep(for: .seconds(1))
continue
}
throw error
case .writeFailure(let underlyingError):
if let avError = underlyingError as? AVError,
avError.code == .diskFull {
print("Disk full - cannot retry")
throw error
}
// Other write failures might be transient
print("Write failed, retrying... (attempt \(attempt))")
try await Task.sleep(for: .seconds(2))
continue
case .setupFailure:
// Setup failures are usually permanent
throw error
}
}
}
throw lastError ?? ExportSession.Error.setupFailure(.videoTracksEmpty)
}
```
## Cancellation Handling
### Handling Task Cancellation
```swift
func exportWithCancellation() async throws {
let exporter = ExportSession()
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
} catch is CancellationError {
print("Export was cancelled")
// Clean up partial files if needed
try? FileManager.default.removeItem(at: destinationURL)
}
}
// Cancel from another task
let exportTask = Task {
try await exportWithCancellation()
}
// Later...
exportTask.cancel()
```
## Validation Before Export
### Pre-Export Validation
```swift
func validateBeforeExport(asset: AVAsset, destinationURL: URL) async throws {
// Check video tracks
let videoTracks = try await asset.loadTracks(withMediaType: .video)
guard !videoTracks.isEmpty else {
throw ExportSession.Error.setupFailure(.videoTracksEmpty)
}
// Check disk space
let resourceValues = try destinationURL.resourceValues(forKeys: [.volumeAvailableCapacityKey])
if let availableCapacity = resourceValues.volumeAvailableCapacity {
let estimatedSize = try await estimateOutputSize(for: asset)
guard availableCapacity > estimatedSize else {
throw ExportSession.Error.writeFailure(AVError(.diskFull))
}
}
// Check destination directory exists
let destinationDir = destinationURL.deletingLastPathComponent()
guard FileManager.default.fileExists(atPath: destinationDir.path) else {
throw ExportSession.Error.writeFailure(AVError(.fileNotFound))
}
// Check write permissions
guard FileManager.default.isWritableFile(atPath: destinationDir.path) else {
throw ExportSession.Error.writeFailure(AVError(.noPermission))
}
}
private func estimateOutputSize(for asset: AVAsset) async throws -> Int64 {
let duration = try await asset.load(.duration)
let durationSeconds = duration.seconds
// Rough estimate: 5 Mbps for 1080p H.264
let estimatedBitrate = 5_000_000 // bits per second
let estimatedBytes = Int64(durationSeconds * Double(estimatedBitrate) / 8)
return estimatedBytes
}
```
## User-Friendly Error Messages
### Providing Helpful Messages
```swift
func userFriendlyErrorMessage(for error: ExportSession.Error) -> String {
switch error {
case .setupFailure(.videoTracksEmpty):
return "The selected file doesn't contain any video content."
case .setupFailure(.audioSettingsInvalid):
return "The audio settings are not compatible with the selected format."
case .setupFailure(.videoSettingsInvalid):
return "The video settings are not compatible with the selected format."
case .readFailure(let underlyingError):
if let avError = underlyingError as? AVError {
switch avError.code {
case .fileFormatNotRecognized:
return "The video file format is not supported."
case .mediaServicesWereReset:
return "Media services were interrupted. Please try again."
default:
return "Unable to read the source video file."
}
}
return "Unable to read the source video file."
case .writeFailure(let underlyingError):
if let avError = underlyingError as? AVError {
switch avError.code {
case .diskFull:
return "Not enough storage space to complete the export."
case .noPermission:
return "Permission denied. Check that you can write to the destination folder."
default:
return "Unable to save the exported video file."
}
}
return "Unable to save the exported video file."
}
}
```
## Debugging Tips
### Enable Detailed Logging
```swift
// Add logging to track export progress
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Export progress: \(String(format: "%.1f", progress * 100))%")
}
}
print("Starting export...")
print("Source: \(sourceAsset)")
print("Destination: \(destinationURL)")
do {
try await exporter.export(/* ... */)
print("Export completed successfully")
} catch {
print("Export failed: \(error)")
}
```
### Check Asset Properties
```swift
func debugAssetProperties(asset: AVAsset) async {
do {
let duration = try await asset.load(.duration)
let videoTracks = try await asset.loadTracks(withMediaType: .video)
let audioTracks = try await asset.loadTracks(withMediaType: .audio)
print("Asset duration: \(duration.seconds) seconds")
print("Video tracks: \(videoTracks.count)")
print("Audio tracks: \(audioTracks.count)")
for (index, track) in videoTracks.enumerated() {
let naturalSize = try await track.load(.naturalSize)
let nominalFrameRate = try await track.load(.nominalFrameRate)
print("Video track \(index): \(naturalSize) @ \(nominalFrameRate) fps")
}
} catch {
print("Failed to load asset properties: \(error)")
}
}
```
## See Also
- ``ExportSession/Error`` - Main error enum
- ``ExportSession/SetupFailureReason`` - Setup failure details
- <doc:GettingStarted> - Basic usage examples
- <doc:PerformanceOptimization> - Avoiding common performance issues

View file

@ -0,0 +1,336 @@
# Exporting Videos
Comprehensive guide to video export scenarios and best practices.
## Overview
This guide covers various video export scenarios, from simple conversions to complex multi-track compositions with custom settings.
## Basic Video Export
### Simple Format Conversion
Convert between video formats while maintaining quality:
```swift
let exporter = ExportSession()
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
### Changing Resolution
Scale video to different resolutions:
```swift
// 4K to 1080p
try await exporter.export(
asset: source4KAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
// 1080p to 720p
try await exporter.export(
asset: source1080pAsset,
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
## Advanced Video Configuration
### High-Quality Exports
For maximum quality, use HEVC with high bitrates:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 3840, height: 2160)
.fps(60)
.bitrate(20_000_000) // 20 Mbps
.color(.hdr),
to: destinationURL,
as: .mov
)
```
### Optimized for Social Media
Twitter-optimized export:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720)
.fps(30)
.bitrate(2_000_000), // 2 Mbps
to: destinationURL,
as: .mp4
)
```
Instagram-optimized export:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1080, height: 1080) // Square aspect ratio
.fps(30)
.bitrate(3_500_000), // 3.5 Mbps
to: destinationURL,
as: .mp4
)
```
### Web Streaming
Optimize for web playback with multiple bitrates:
```swift
// Low bitrate for mobile
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
video: .codec(.h264, width: 854, height: 480)
.fps(24)
.bitrate(800_000), // 800 Kbps
to: lowQualityURL,
as: .mp4
)
// High bitrate for desktop
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
video: .codec(.h264, width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000), // 5 Mbps
to: highQualityURL,
as: .mp4
)
```
## Working with Time Ranges
### Creating Clips
Extract specific segments from longer videos:
```swift
// First 30 seconds
let clipRange = CMTimeRange(
start: .zero,
duration: CMTime(seconds: 30, preferredTimescale: 600)
)
try await exporter.export(
asset: sourceAsset,
timeRange: clipRange,
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
### Removing Sections
Skip a middle section by exporting multiple clips:
```swift
// Export first part (0-60 seconds)
let part1Range = CMTimeRange(
start: .zero,
duration: CMTime(seconds: 60, preferredTimescale: 600)
)
// Export second part (120 seconds to end)
let part2Start = CMTime(seconds: 120, preferredTimescale: 600)
let totalDuration = try await sourceAsset.load(.duration)
let part2Range = CMTimeRange(
start: part2Start,
duration: totalDuration - part2Start
)
// Export each part separately
try await exporter.export(
asset: sourceAsset,
timeRange: part1Range,
video: .codec(.h264, width: 1280, height: 720),
to: part1URL,
as: .mp4
)
try await exporter.export(
asset: sourceAsset,
timeRange: part2Range,
video: .codec(.h264, width: 1280, height: 720),
to: part2URL,
as: .mp4
)
```
## Color Management
### Standard Dynamic Range (SDR)
For compatibility with most devices:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
### High Dynamic Range (HDR)
Preserve HDR content for compatible displays:
```swift
try await exporter.export(
asset: hdrSourceAsset,
video: .codec(.hevc, width: 3840, height: 2160)
.color(.hdr),
to: destinationURL,
as: .mov
)
```
## File Format Considerations
### MP4 vs MOV
**MP4** - Best for:
- Web streaming
- Mobile devices
- General compatibility
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
**MOV** - Best for:
- Professional workflows
- HDR content
- Apple ecosystem
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 3840, height: 2160),
to: destinationURL,
as: .mov
)
```
## Performance Optimization
### Choosing Appropriate Settings
Balance quality and performance based on your use case:
```swift
// Fast export (lower quality)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.baseline30), width: 1280, height: 720)
.fps(24)
.bitrate(1_500_000),
to: destinationURL,
as: .mp4
)
// Balanced export
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.main32), width: 1920, height: 1080)
.fps(30)
.bitrate(4_000_000),
to: destinationURL,
as: .mp4
)
// High quality export (slower)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.high41), width: 1920, height: 1080)
.fps(60)
.bitrate(8_000_000),
to: destinationURL,
as: .mp4
)
```
### Progress Monitoring
Provide user feedback during long exports:
```swift
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
await MainActor.run {
progressView.progress = progress
progressLabel.text = "\(Int(progress * 100))%"
}
}
}
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
## Error Handling
Always wrap exports in proper error handling:
```swift
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
print("Export completed successfully")
} catch let error as ExportSession.Error {
switch error {
case .setupFailure(let reason):
print("Setup failed: \(reason)")
case .readFailure(let underlyingError):
print("Read failed: \(underlyingError?.localizedDescription ?? "Unknown")")
case .writeFailure(let underlyingError):
print("Write failed: \(underlyingError?.localizedDescription ?? "Unknown")")
}
} catch {
print("Unexpected error: \(error)")
}
```
## See Also
- <doc:AudioConfiguration> - Adding audio to your exports
- <doc:CustomSettings> - Using raw settings dictionaries
- <doc:PerformanceOptimization> - Optimizing export performance

View file

@ -0,0 +1,158 @@
# Getting Started
Learn how to quickly set up and use SJSAssetExportSession for video exports.
## Overview
SJSAssetExportSession provides a simple yet powerful way to export videos with custom settings. This guide will walk you through the basic setup and your first export.
## Installation
Add SJSAssetExportSession to your project using Swift Package Manager:
```swift
dependencies: [
.package(url: "https://github.com/samhuri/SJSAssetExportSession.git", from: "0.3.0")
]
```
## Basic Usage
### Step 1: Import the Framework
```swift
import SJSAssetExportSession
import AVFoundation
```
### Step 2: Create an Export Session
```swift
let exporter = ExportSession()
```
### Step 3: Prepare Your Asset
```swift
let sourceURL = URL(fileURLWithPath: "path/to/your/video.mov")
let sourceAsset = AVURLAsset(url: sourceURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: true
])
```
> Important: Always use `AVURLAssetPreferPreciseDurationAndTimingKey: true` for accurate duration and timing information.
### Step 4: Define Your Output
```swift
let destinationURL = URL.temporaryDirectory.appending(component: "exported-video.mp4")
```
### Step 5: Export with Basic Settings
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
## Complete Example
Here's a complete example that includes progress monitoring:
```swift
import SJSAssetExportSession
import AVFoundation
func exportVideo() async throws {
let sourceURL = URL(fileURLWithPath: "input.mov")
let sourceAsset = AVURLAsset(url: sourceURL, options: [
AVURLAssetPreferPreciseDurationAndTimingKey: true
])
let destinationURL = URL.temporaryDirectory.appending(component: "output.mp4")
let exporter = ExportSession()
// Monitor progress
Task {
for await progress in exporter.progressStream {
print("Export progress: \(Int(progress * 100))%")
}
}
// Perform the export
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000),
to: destinationURL,
as: .mp4
)
print("Export completed successfully!")
}
```
## Next Steps
- Learn about <doc:AudioConfiguration> to customize audio settings
- Explore <doc:VideoConfiguration> for advanced video options
- Check out <doc:ExportingVideos> for more complex scenarios
- Read about <doc:ErrorHandling> to handle export failures gracefully
## Common Patterns
### Exporting a Video Clip
To export only a portion of a video:
```swift
try await exporter.export(
asset: sourceAsset,
timeRange: CMTimeRange(
start: CMTime(seconds: 10, preferredTimescale: 600),
duration: CMTime(seconds: 30, preferredTimescale: 600)
),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
### Adding Metadata
Include metadata in your exported video:
```swift
let titleMetadata = AVMutableMetadataItem()
titleMetadata.key = AVMetadataKey.commonKeyTitle.rawValue as NSString
titleMetadata.keySpace = .common
titleMetadata.value = "My Video Title" as NSString
try await exporter.export(
asset: sourceAsset,
metadata: [titleMetadata],
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
### Optimizing for Network Playback
For videos that will be streamed or downloaded:
```swift
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```

View file

@ -0,0 +1,581 @@
# Performance Optimization
Learn how to optimize export performance and handle large video files efficiently.
## Overview
Export performance depends on many factors including source file characteristics, output settings, device capabilities, and system resources. This guide covers strategies to optimize export speed while maintaining quality.
## Understanding Performance Factors
### Key Performance Variables
1. **Source Resolution**: Higher resolution sources require more processing
2. **Output Resolution**: Scaling affects performance
3. **Codec Choice**: H.264 vs HEVC vs other codecs
4. **Bitrate**: Higher bitrates require more processing
5. **Frame Rate**: Higher frame rates increase workload
6. **Device Capabilities**: CPU, GPU, and available memory
### Performance vs Quality Trade-offs
```swift
// Fast export (lower quality)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.baseline30), width: 1280, height: 720)
.fps(24)
.bitrate(1_500_000),
to: destinationURL,
as: .mp4
)
// Balanced export
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.main32), width: 1920, height: 1080)
.fps(30)
.bitrate(4_000_000),
to: destinationURL,
as: .mp4
)
// High quality (slower)
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 3840, height: 2160)
.fps(60)
.bitrate(20_000_000),
to: destinationURL,
as: .mov
)
```
## Codec Optimization
### H.264 Profile Selection
Choose the appropriate H.264 profile for your performance needs:
```swift
// Fastest encoding - Baseline profile
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.baselineAuto), width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
// Balanced - Main profile
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.mainAuto), width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
// Best compression (slower) - High profile
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.highAuto), width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
### HEVC Considerations
HEVC provides better compression but requires more processing power:
```swift
// Use HEVC only when:
// 1. Target devices support it
// 2. File size is more important than encoding speed
// 3. You have sufficient processing power
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 1920, height: 1080)
.bitrate(3_000_000), // Lower bitrate than H.264 for same quality
to: destinationURL,
as: .mp4
)
```
## Resolution and Scaling Optimization
### Intelligent Resolution Selection
```swift
func optimizedResolution(for sourceAsset: AVAsset) async throws -> CGSize {
let videoTracks = try await sourceAsset.loadTracks(withMediaType: .video)
guard let firstTrack = videoTracks.first else {
throw ExportSession.Error.setupFailure(.videoTracksEmpty)
}
let naturalSize = try await firstTrack.load(.naturalSize)
// Don't upscale - only downscale for performance
if naturalSize.width <= 1280 && naturalSize.height <= 720 {
return naturalSize
} else if naturalSize.width <= 1920 && naturalSize.height <= 1080 {
return CGSize(width: 1280, height: 720) // Downscale to 720p
} else {
return CGSize(width: 1920, height: 1080) // Downscale to 1080p
}
}
// Usage
let optimalSize = try await optimizedResolution(for: sourceAsset)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, size: optimalSize),
to: destinationURL,
as: .mp4
)
```
### Avoiding Unnecessary Scaling
```swift
// Check if scaling is needed
func needsScaling(sourceSize: CGSize, targetSize: CGSize) -> Bool {
return sourceSize.width != targetSize.width || sourceSize.height != targetSize.height
}
// Match source resolution when possible
let videoTracks = try await sourceAsset.loadTracks(withMediaType: .video)
if let track = videoTracks.first {
let naturalSize = try await track.load(.naturalSize)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, size: naturalSize), // No scaling needed
to: destinationURL,
as: .mp4
)
}
```
## Frame Rate Optimization
### Source-Based Frame Rate Selection
```swift
func optimizedFrameRate(for asset: AVAsset) async throws -> Int? {
let videoTracks = try await asset.loadTracks(withMediaType: .video)
guard let track = videoTracks.first else { return nil }
let nominalFrameRate = try await track.load(.nominalFrameRate)
// Use source frame rate or a common divisor
switch nominalFrameRate {
case 0..<25:
return 24
case 25..<30:
return 25
case 30..<50:
return 30
case 50..<60:
return 50
default:
return 60
}
}
// Usage
let optimalFPS = try await optimizedFrameRate(for: sourceAsset)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(optimalFPS ?? 30),
to: destinationURL,
as: .mp4
)
```
## Memory Management
### Handling Large Files
```swift
func exportLargeFile(asset: AVAsset) async throws {
// Check available memory
let processInfo = ProcessInfo.processInfo
let physicalMemory = processInfo.physicalMemory
// Adjust settings based on available memory
let videoSettings: VideoOutputSettings
if physicalMemory < 4_000_000_000 { // Less than 4GB
videoSettings = .codec(.h264(.baseline31), width: 1280, height: 720)
.fps(24)
.bitrate(2_000_000)
} else if physicalMemory < 8_000_000_000 { // Less than 8GB
videoSettings = .codec(.h264(.main32), width: 1920, height: 1080)
.fps(30)
.bitrate(4_000_000)
} else {
videoSettings = .codec(.h264(.high40), width: 1920, height: 1080)
.fps(30)
.bitrate(6_000_000)
}
try await exporter.export(
asset: asset,
video: videoSettings,
to: destinationURL,
as: .mp4
)
}
```
### Memory-Efficient Settings
```swift
// Use raw settings for fine-grained memory control
let memoryEfficientVideoSettings: [String: any Sendable] = [
AVVideoCodecKey: AVVideoCodecType.h264.rawValue,
AVVideoWidthKey: NSNumber(value: 1280),
AVVideoHeightKey: NSNumber(value: 720),
AVVideoCompressionPropertiesKey: [
AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel,
AVVideoAverageBitRateKey: NSNumber(value: 2_000_000),
AVVideoMaxKeyFrameIntervalKey: NSNumber(value: 30),
AVVideoAllowFrameReorderingKey: NSNumber(value: false), // Reduces memory usage
AVVideoExpectedSourceFrameRateKey: NSNumber(value: 24)
] as [String: any Sendable]
]
try await exporter.export(
asset: sourceAsset,
audioOutputSettings: AudioOutputSettings.default.settingsDictionary,
videoOutputSettings: memoryEfficientVideoSettings,
to: destinationURL,
as: .mp4
)
```
## Parallel Processing
### Batch Export Optimization
```swift
class OptimizedBatchExporter {
private let maxConcurrentExports: Int
init(maxConcurrentExports: Int = 2) {
// Limit concurrent exports based on system capabilities
let processorCount = ProcessInfo.processInfo.processorCount
self.maxConcurrentExports = min(maxConcurrentExports, max(1, processorCount / 2))
}
func exportFiles(_ files: [(asset: AVAsset, url: URL)]) async throws {
// Process files in chunks to avoid overwhelming the system
for chunk in files.chunked(into: maxConcurrentExports) {
try await withThrowingTaskGroup(of: Void.self) { group in
for file in chunk {
group.addTask {
let exporter = ExportSession()
try await exporter.export(
asset: file.asset,
video: .codec(.h264, width: 1280, height: 720),
to: file.url,
as: .mp4
)
}
}
try await group.waitForAll()
}
}
}
}
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0..<min($0 + size, count)])
}
}
}
```
## Device-Specific Optimization
### iOS Device Capabilities
```swift
import UIKit
func deviceOptimizedSettings() -> VideoOutputSettings {
let device = UIDevice.current
// Check device capabilities
if device.userInterfaceIdiom == .pad {
// iPad - more processing power
return .codec(.h264(.high40), width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000)
} else {
// iPhone - optimize for battery and heat
return .codec(.h264(.main32), width: 1280, height: 720)
.fps(30)
.bitrate(3_000_000)
}
}
```
### macOS Optimization
```swift
#if os(macOS)
import IOKit
func macOptimizedSettings() -> VideoOutputSettings {
// Check for discrete GPU
let hasDiscreteGPU = hasDiscreteGraphics()
if hasDiscreteGPU {
// Use higher settings with discrete GPU
return .codec(.hevc, width: 3840, height: 2160)
.fps(30)
.bitrate(15_000_000)
} else {
// Conservative settings for integrated graphics
return .codec(.h264(.main32), width: 1920, height: 1080)
.fps(30)
.bitrate(4_000_000)
}
}
private func hasDiscreteGraphics() -> Bool {
// Implementation to check for discrete GPU
// This is a simplified check - actual implementation would be more complex
return false
}
#endif
```
## Monitoring and Profiling
### Performance Metrics
```swift
class PerformanceMonitor {
private var startTime: Date?
private var startMemory: UInt64?
func startMonitoring() {
startTime = Date()
startMemory = getCurrentMemoryUsage()
}
func endMonitoring(fileSize: UInt64) -> PerformanceReport {
let endTime = Date()
let endMemory = getCurrentMemoryUsage()
let duration = endTime.timeIntervalSince(startTime ?? endTime)
let memoryDelta = endMemory - (startMemory ?? 0)
let processingSpeed = Double(fileSize) / duration // bytes per second
return PerformanceReport(
duration: duration,
memoryUsed: memoryDelta,
processingSpeed: processingSpeed
)
}
private func getCurrentMemoryUsage() -> UInt64 {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(mach_task_self_,
task_flavor_t(MACH_TASK_BASIC_INFO),
$0,
&count)
}
}
if kerr == KERN_SUCCESS {
return info.resident_size
} else {
return 0
}
}
}
struct PerformanceReport {
let duration: TimeInterval
let memoryUsed: UInt64
let processingSpeed: Double // bytes per second
var mbPerSecond: Double {
return processingSpeed / 1_000_000
}
}
```
### Usage Example
```swift
func monitoredExport() async throws {
let monitor = PerformanceMonitor()
monitor.startMonitoring()
let exporter = ExportSession()
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
let fileSize = try FileManager.default.attributesOfItem(atPath: destinationURL.path)[.size] as? UInt64 ?? 0
let report = monitor.endMonitoring(fileSize: fileSize)
print("Export completed in \(report.duration)s")
print("Processing speed: \(report.mbPerSecond) MB/s")
print("Memory used: \(report.memoryUsed / 1_000_000) MB")
}
```
## Common Performance Issues
### Avoiding Common Pitfalls
1. **Don't upscale unnecessarily**:
```swift
// Bad: Upscaling from 720p to 4K
try await exporter.export(
asset: sourceAsset720p,
video: .codec(.h264, width: 3840, height: 2160), // Unnecessary upscaling
to: destinationURL,
as: .mp4
)
// Good: Maintain source resolution
try await exporter.export(
asset: sourceAsset720p,
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
2. **Choose appropriate bitrates**:
```swift
// Bad: Excessive bitrate for resolution
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720)
.bitrate(20_000_000), // Too high for 720p
to: destinationURL,
as: .mp4
)
// Good: Appropriate bitrate
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720)
.bitrate(2_500_000), // Suitable for 720p
to: destinationURL,
as: .mp4
)
```
3. **Consider frame rate needs**:
```swift
// Bad: Unnecessary high frame rate
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(120), // Overkill for most content
to: destinationURL,
as: .mp4
)
// Good: Standard frame rate
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(30), // Standard and efficient
to: destinationURL,
as: .mp4
)
```
## Adaptive Quality Selection
### Dynamic Settings Based on Source
```swift
func adaptiveExportSettings(for asset: AVAsset) async throws -> VideoOutputSettings {
let videoTracks = try await asset.loadTracks(withMediaType: .video)
guard let track = videoTracks.first else {
throw ExportSession.Error.setupFailure(.videoTracksEmpty)
}
let naturalSize = try await track.load(.naturalSize)
let nominalFrameRate = try await track.load(.nominalFrameRate)
let estimatedDataRate = try await track.load(.estimatedDataRate)
// Calculate appropriate output settings
let outputWidth = min(Int(naturalSize.width), 1920)
let outputHeight = min(Int(naturalSize.height), 1080)
let outputFPS = min(Int(nominalFrameRate), 30)
let outputBitrate = min(Int(estimatedDataRate), 5_000_000)
return .codec(.h264(.mainAuto), width: outputWidth, height: outputHeight)
.fps(outputFPS)
.bitrate(outputBitrate)
}
// Usage
let settings = try await adaptiveExportSettings(for: sourceAsset)
try await exporter.export(
asset: sourceAsset,
video: settings,
to: destinationURL,
as: .mp4
)
```
## Testing Performance
### Benchmarking Different Settings
```swift
func benchmarkSettings() async throws {
let testCases: [(name: String, settings: VideoOutputSettings)] = [
("Fast", .codec(.h264(.baseline30), width: 1280, height: 720).fps(24).bitrate(1_500_000)),
("Balanced", .codec(.h264(.main32), width: 1920, height: 1080).fps(30).bitrate(4_000_000)),
("Quality", .codec(.h264(.high40), width: 1920, height: 1080).fps(30).bitrate(8_000_000)),
("HEVC", .codec(.hevc, width: 1920, height: 1080).fps(30).bitrate(3_000_000))
]
for testCase in testCases {
let startTime = Date()
let exporter = ExportSession()
try await exporter.export(
asset: sourceAsset,
video: testCase.settings,
to: URL.temporaryDirectory.appending(component: "\(testCase.name).mp4"),
as: .mp4
)
let duration = Date().timeIntervalSince(startTime)
print("\(testCase.name): \(duration)s")
}
}
```
## See Also
- <doc:VideoConfiguration> - Video settings options
- <doc:AudioConfiguration> - Audio settings optimization
- <doc:ProgressTracking> - Monitoring export progress
- <doc:ErrorHandling> - Handling performance-related errors

View file

@ -0,0 +1,566 @@
# Progress Tracking
Learn how to monitor export progress and provide user feedback during video processing.
## Overview
SJSAssetExportSession provides real-time progress tracking through an `AsyncStream<Float>`. This allows you to create responsive user interfaces that show export progress, estimated time remaining, and handle user cancellation.
## Basic Progress Monitoring
### Simple Progress Tracking
```swift
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
print("Export progress: \(Int(progress * 100))%")
}
}
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
### Progress Values
The progress stream yields `Float` values between `0.0` and `1.0`:
- `0.0`: Export has just started
- `0.5`: Export is halfway complete
- `1.0`: Export has finished successfully
## UI Integration
### UIKit Progress View
```swift
import UIKit
class ExportViewController: UIViewController {
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var progressLabel: UILabel!
@IBOutlet weak var cancelButton: UIButton!
private var exportTask: Task<Void, Error>?
func startExport() {
let exporter = ExportSession()
exportTask = Task {
// Monitor progress on main thread
Task { @MainActor in
for await progress in exporter.progressStream {
progressView.progress = progress
progressLabel.text = "\(Int(progress * 100))%"
}
}
// Perform export
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
await MainActor.run {
progressLabel.text = "Complete!"
cancelButton.isEnabled = false
}
}
}
@IBAction func cancelExport() {
exportTask?.cancel()
exportTask = nil
}
}
```
### SwiftUI Progress View
```swift
import SwiftUI
struct ExportView: View {
@State private var progress: Float = 0.0
@State private var isExporting = false
@State private var exportTask: Task<Void, Error>?
var body: some View {
VStack(spacing: 20) {
if isExporting {
ProgressView("Exporting...", value: progress, total: 1.0)
.progressViewStyle(LinearProgressViewStyle())
Text("\(Int(progress * 100))%")
.font(.headline)
Button("Cancel") {
exportTask?.cancel()
exportTask = nil
isExporting = false
}
.foregroundColor(.red)
} else {
Button("Start Export") {
startExport()
}
}
}
.padding()
}
private func startExport() {
isExporting = true
progress = 0.0
let exporter = ExportSession()
exportTask = Task {
// Monitor progress
Task { @MainActor in
for await progressValue in exporter.progressStream {
progress = progressValue
}
}
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
await MainActor.run {
isExporting = false
progress = 1.0
}
} catch {
await MainActor.run {
isExporting = false
// Handle error
}
}
}
}
}
```
## Advanced Progress Features
### Time Estimation
```swift
class ExportProgressTracker {
private var startTime: Date?
private var lastProgressTime: Date?
private var lastProgress: Float = 0.0
func trackProgress(_ progress: Float) -> (timeElapsed: TimeInterval, timeRemaining: TimeInterval?) {
let now = Date()
if startTime == nil {
startTime = now
}
let timeElapsed = now.timeIntervalSince(startTime!)
// Calculate time remaining based on progress rate
let timeRemaining: TimeInterval?
if progress > 0 && progress != lastProgress {
let progressRate = progress / Float(timeElapsed)
let remainingProgress = 1.0 - progress
timeRemaining = TimeInterval(remainingProgress / progressRate)
} else {
timeRemaining = nil
}
lastProgressTime = now
lastProgress = progress
return (timeElapsed, timeRemaining)
}
}
// Usage
let progressTracker = ExportProgressTracker()
Task {
for await progress in exporter.progressStream {
let (elapsed, remaining) = progressTracker.trackProgress(progress)
await MainActor.run {
progressView.progress = progress
progressLabel.text = "\(Int(progress * 100))%"
if let remaining = remaining {
timeLabel.text = "Time remaining: \(Int(remaining))s"
}
}
}
}
```
### Progress with Detailed Status
```swift
enum ExportStatus {
case preparing
case encoding(Float)
case finalizing
case completed
case failed(Error)
case cancelled
}
class DetailedExportTracker: ObservableObject {
@Published var status: ExportStatus = .preparing
func startExport() {
status = .preparing
let exporter = ExportSession()
Task {
// Monitor progress
Task { @MainActor in
for await progress in exporter.progressStream {
if progress < 1.0 {
status = .encoding(progress)
} else {
status = .finalizing
}
}
}
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
await MainActor.run {
status = .completed
}
} catch is CancellationError {
await MainActor.run {
status = .cancelled
}
} catch {
await MainActor.run {
status = .failed(error)
}
}
}
}
}
```
## Batch Export Progress
### Multiple File Export
```swift
class BatchExportManager: ObservableObject {
@Published var overallProgress: Float = 0.0
@Published var currentFileProgress: Float = 0.0
@Published var currentFileName: String = ""
@Published var filesCompleted: Int = 0
func exportFiles(_ files: [(asset: AVAsset, url: URL, name: String)]) async throws {
let totalFiles = files.count
for (index, file) in files.enumerated() {
await MainActor.run {
currentFileName = file.name
currentFileProgress = 0.0
filesCompleted = index
}
let exporter = ExportSession()
// Track individual file progress
Task { @MainActor in
for await progress in exporter.progressStream {
currentFileProgress = progress
// Calculate overall progress
let completedPortion = Float(index) / Float(totalFiles)
let currentPortion = progress / Float(totalFiles)
overallProgress = completedPortion + currentPortion
}
}
try await exporter.export(
asset: file.asset,
video: .codec(.h264, width: 1920, height: 1080),
to: file.url,
as: .mp4
)
}
await MainActor.run {
overallProgress = 1.0
filesCompleted = totalFiles
}
}
}
```
## Background Export Progress
### Handling Background Tasks
```swift
import BackgroundTasks
class BackgroundExportManager {
private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
func startBackgroundExport() {
// Request background time
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "VideoExport") {
// Background time expired
self.endBackgroundTask()
}
let exporter = ExportSession()
Task {
// Monitor progress with reduced frequency for background
Task {
var lastUpdate = Date()
for await progress in exporter.progressStream {
let now = Date()
if now.timeIntervalSince(lastUpdate) > 1.0 { // Update every second
print("Background export progress: \(Int(progress * 100))%")
lastUpdate = now
}
}
}
do {
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
print("Background export completed")
} catch {
print("Background export failed: \(error)")
}
endBackgroundTask()
}
}
private func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = .invalid
}
}
```
## Progress Persistence
### Saving Progress State
```swift
class PersistentExportManager {
private let userDefaults = UserDefaults.standard
private let progressKey = "export_progress"
func saveProgress(_ progress: Float, for exportID: String) {
userDefaults.set(progress, forKey: "\(progressKey)_\(exportID)")
}
func loadProgress(for exportID: String) -> Float {
return userDefaults.float(forKey: "\(progressKey)_\(exportID)")
}
func clearProgress(for exportID: String) {
userDefaults.removeObject(forKey: "\(progressKey)_\(exportID)")
}
func resumableExport(exportID: String) async throws {
let savedProgress = loadProgress(for: exportID)
if savedProgress > 0 {
print("Resuming export from \(Int(savedProgress * 100))%")
}
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
saveProgress(progress, for: exportID)
await MainActor.run {
// Update UI
}
}
}
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
clearProgress(for: exportID)
}
}
```
## Performance Considerations
### Throttling Progress Updates
```swift
func throttledProgressTracking() {
let exporter = ExportSession()
Task {
var lastUpdate = Date()
let updateInterval: TimeInterval = 0.1 // Update every 100ms
for await progress in exporter.progressStream {
let now = Date()
if now.timeIntervalSince(lastUpdate) >= updateInterval {
await MainActor.run {
progressView.progress = progress
progressLabel.text = "\(Int(progress * 100))%"
}
lastUpdate = now
}
}
}
try await exporter.export(/* ... */)
}
```
### Memory-Efficient Progress Tracking
```swift
func efficientProgressTracking() {
let exporter = ExportSession()
Task {
// Use AsyncSequence operations to process progress efficiently
for await progress in exporter.progressStream
.compactMap { progress in
// Only emit significant progress changes
progress.isMultiple(of: 0.01) ? progress : nil
} {
await MainActor.run {
updateProgress(progress)
}
}
}
}
```
## Testing Progress Tracking
### Mock Progress Stream
```swift
extension ExportSession {
static func mockProgressStream(duration: TimeInterval = 5.0) -> AsyncStream<Float> {
AsyncStream { continuation in
Task {
let steps = 100
let interval = duration / Double(steps)
for step in 0...steps {
let progress = Float(step) / Float(steps)
continuation.yield(progress)
if step < steps {
try await Task.sleep(for: .seconds(interval))
}
}
continuation.finish()
}
}
}
}
// Usage in tests
func testProgressTracking() async {
var progressValues: [Float] = []
for await progress in ExportSession.mockProgressStream(duration: 1.0) {
progressValues.append(progress)
}
XCTAssertEqual(progressValues.first, 0.0)
XCTAssertEqual(progressValues.last, 1.0)
XCTAssertTrue(progressValues.count > 50) // Should have many progress updates
}
```
## Common Patterns
### Progress with User Feedback
```swift
func exportWithFeedback() async throws {
let exporter = ExportSession()
let startTime = Date()
Task { @MainActor in
for await progress in exporter.progressStream {
let elapsed = Date().timeIntervalSince(startTime)
if progress > 0 {
let estimatedTotal = elapsed / Double(progress)
let remaining = estimatedTotal - elapsed
progressLabel.text = """
\(Int(progress * 100))% complete
Time remaining: \(formatTime(remaining))
"""
} else {
progressLabel.text = "Starting export..."
}
progressView.progress = progress
}
}
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
}
private func formatTime(_ seconds: TimeInterval) -> String {
let minutes = Int(seconds) / 60
let seconds = Int(seconds) % 60
return String(format: "%d:%02d", minutes, seconds)
}
```
## See Also
- ``ExportSession`` - Main export class with progress stream
- <doc:ErrorHandling> - Handling errors during progress tracking
- <doc:PerformanceOptimization> - Optimizing export performance
- <doc:GettingStarted> - Basic usage examples

View file

@ -1,49 +1,133 @@
# ``SJSAssetExportSession``
`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`.
A Swift-first alternative to AVAssetExportSession with custom audio/video settings and strict concurrency support.
[`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.
## Overview
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.
`SJSAssetExportSession` is a modern Swift package that provides an alternative to `AVAssetExportSession` with full control over audio and video export settings. Unlike the built-in `AVAssetExportSession`, this library allows you to specify custom codec settings, bitrates, frame rates, and color properties without having to work directly with `AVAssetReader` and `AVAssetWriter`.
### Key Features
- **Two-tier API Design**: Choose between a simple builder pattern or raw settings dictionaries for maximum flexibility
- **Swift 6 Strict Concurrency**: Built from the ground up with `Sendable` types and async/await
- **Real-time Progress Reporting**: Monitor export progress via `AsyncStream<Float>`
- **Comprehensive Format Support**: H.264, HEVC, AAC, MP3, and more
- **Advanced Color Management**: Support for both SDR (BT.709) and HDR (BT.2020) workflows
- **Mix-and-Match Approach**: Bootstrap custom settings from builder patterns for ultimate flexibility
### Why SJSAssetExportSession?
[`AVAssetExportSession`][AV] provides limited customization options, essentially restricting you to the presets it offers. This package gives you the control you need while maintaining a simple, Swift-friendly API.
[AV]: https://developer.apple.com/documentation/avfoundation/avassetexportsession
[SDAV]: https://github.com/rs/SDAVAssetExportSession
Instead of wrestling with complex [audio settings][] and [video settings][] dictionaries, you can use the builder pattern to construct exactly what you need:
[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)")
print("Export 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"),
asset: sourceAsset,
audio: .format(.aac).channels(2).sampleRate(48_000),
video: .codec(.h264, width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
## Getting Started
### Basic Export
The simplest way to get started is with a basic video export:
```swift
let exporter = ExportSession()
try await exporter.export(
asset: AVURLAsset(url: sourceURL),
video: .codec(.h264, width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
```
### Monitoring Progress
Track export progress using the built-in progress stream:
```swift
let exporter = ExportSession()
Task {
for await progress in exporter.progressStream {
DispatchQueue.main.async {
progressView.progress = progress
}
}
}
try await exporter.export(/* ... */)
```
### Advanced Configuration
For more control, specify custom audio settings, metadata, and time ranges:
```swift
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
metadata: [locationMetadata],
timeRange: CMTimeRange(start: .zero, duration: .seconds(30)),
audio: .format(.mp3).channels(1).sampleRate(22_050),
video: .codec(.hevc, width: 3840, height: 2160)
.fps(60)
.bitrate(15_000_000)
.color(.hdr),
to: destinationURL,
as: .mov
)
```
## Topics
### Exporting
### Essentials
- ``ExportSession``
- ``ExportSession/Error``
- ``ExportSession/SetupFailureReason``
- <doc:GettingStarted>
- <doc:ExportingVideos>
### Audio Output Settings
### Audio Configuration
- ``AudioOutputSettings``
- ``AudioOutputSettings/Format``
- <doc:AudioConfiguration>
### Video Output Settings
### Video Configuration
- ``VideoOutputSettings``
- ``VideoOutputSettings/Codec``
- ``VideoOutputSettings/H264Profile``
- ``VideoOutputSettings/Color``
- <doc:VideoConfiguration>
### Error Handling
- ``ExportSession/Error``
- ``ExportSession/SetupFailureReason``
- <doc:ErrorHandling>
### Advanced Topics
- <doc:CustomSettings>
- <doc:ProgressTracking>
- <doc:PerformanceOptimization>

View file

@ -0,0 +1,452 @@
# Video Configuration
Comprehensive guide to configuring video settings for optimal export results.
## Overview
SJSAssetExportSession provides extensive video configuration options through the ``VideoOutputSettings`` builder pattern. Configure codecs, resolution, frame rates, bitrates, and color properties to achieve the perfect balance of quality and file size for your use case.
## Basic Video Settings
### Choosing a Codec
Select the appropriate codec for your target platform and quality requirements:
```swift
// H.264 - Maximum compatibility
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
// HEVC - Better compression, newer devices
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 3840, height: 2160),
to: destinationURL,
as: .mov
)
```
### Setting Resolution
Specify exact dimensions for your output video:
```swift
// Common resolutions
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080), // 1080p
to: destinationURL,
as: .mp4
)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720), // 720p
to: destinationURL,
as: .mp4
)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 3840, height: 2160), // 4K
to: destinationURL,
as: .mov
)
```
You can also use `CGSize` for resolution:
```swift
let resolution = CGSize(width: 1920, height: 1080)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, size: resolution),
to: destinationURL,
as: .mp4
)
```
## Frame Rate Configuration
### Standard Frame Rates
Set the output frame rate to match your content or target platform:
```swift
// 24 fps - Cinematic content
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(24),
to: destinationURL,
as: .mp4
)
// 30 fps - Standard video content
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(30),
to: destinationURL,
as: .mp4
)
// 60 fps - High motion content
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(60),
to: destinationURL,
as: .mp4
)
```
### Frame Rate Considerations
- **24 fps**: Film and cinematic content
- **25/30 fps**: Standard broadcast and web video
- **50/60 fps**: Sports, gaming, high-motion content
- **120+ fps**: Slow-motion source material
## Bitrate Configuration
### Quality vs. File Size
Balance video quality with file size using bitrate settings:
```swift
// Low bitrate - Smaller file, lower quality
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1280, height: 720)
.fps(30)
.bitrate(1_000_000), // 1 Mbps
to: destinationURL,
as: .mp4
)
// Medium bitrate - Balanced
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000), // 5 Mbps
to: destinationURL,
as: .mp4
)
// High bitrate - Maximum quality
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(30)
.bitrate(15_000_000), // 15 Mbps
to: destinationURL,
as: .mp4
)
```
### Recommended Bitrates
| Resolution | Frame Rate | Recommended Bitrate |
|------------|------------|-------------------|
| 720p | 30 fps | 2-4 Mbps |
| 1080p | 30 fps | 4-8 Mbps |
| 1080p | 60 fps | 8-12 Mbps |
| 4K | 30 fps | 15-25 Mbps |
| 4K | 60 fps | 25-40 Mbps |
## H.264 Profile Configuration
### Profile Selection
Choose the appropriate H.264 profile for your target devices:
```swift
// Baseline - Maximum compatibility (older devices)
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.baselineAuto), width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
// Main - Good compatibility with better compression
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.mainAuto), width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
// High - Best compression, modern devices
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.highAuto), width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
### Specific Profile Levels
For precise control over encoding parameters:
```swift
// Baseline Level 3.1 - Web compatibility
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.baseline31), width: 1280, height: 720),
to: destinationURL,
as: .mp4
)
// High Level 4.1 - Modern devices
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.high41), width: 1920, height: 1080),
to: destinationURL,
as: .mp4
)
```
## Color Configuration
### Standard Dynamic Range (SDR)
For maximum compatibility across devices:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
SDR uses the BT.709 color space, which corresponds roughly to sRGB and is supported by virtually all displays.
### High Dynamic Range (HDR)
For premium content with enhanced color and brightness:
```swift
try await exporter.export(
asset: hdrSourceAsset,
video: .codec(.hevc, width: 3840, height: 2160)
.color(.hdr),
to: destinationURL,
as: .mov
)
```
HDR uses the BT.2020 color space with HLG transfer function, providing wider color gamut and higher dynamic range.
## Complete Configuration Examples
### Social Media Optimized
Twitter/X optimized export:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.mainAuto), width: 1280, height: 720)
.fps(30)
.bitrate(2_000_000)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
Instagram optimized export:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.mainAuto), width: 1080, height: 1080) // Square
.fps(30)
.bitrate(3_500_000)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
### Professional Video
High-quality export for professional use:
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 3840, height: 2160)
.fps(24)
.bitrate(20_000_000)
.color(.hdr),
to: destinationURL,
as: .mov
)
```
### Web Streaming
Optimized for web playback:
```swift
// Low quality variant
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
video: .codec(.h264(.baseline31), width: 854, height: 480)
.fps(24)
.bitrate(800_000)
.color(.sdr),
to: lowQualityURL,
as: .mp4
)
// High quality variant
try await exporter.export(
asset: sourceAsset,
optimizeForNetworkUse: true,
video: .codec(.h264(.high40), width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000)
.color(.sdr),
to: highQualityURL,
as: .mp4
)
```
## Builder Pattern Chaining
Combine all video settings using method chaining:
```swift
let videoSettings = VideoOutputSettings
.codec(.h264(.highAuto), width: 1920, height: 1080)
.fps(30)
.bitrate(5_000_000)
.color(.sdr)
try await exporter.export(
asset: sourceAsset,
video: videoSettings,
to: destinationURL,
as: .mp4
)
```
## Video Composition Integration
Video settings work seamlessly with AVVideoComposition:
```swift
let videoComposition = try await AVMutableVideoComposition.videoComposition(withPropertiesOf: sourceAsset)
// The video settings will be applied to the composition automatically
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264, width: 1920, height: 1080)
.fps(24)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
## Performance Considerations
### Encoding Speed vs. Quality
- **Baseline Profile**: Fastest encoding, largest file size
- **Main Profile**: Balanced encoding speed and compression
- **High Profile**: Slower encoding, best compression
- **HEVC**: Slowest encoding, best compression for modern devices
### Resolution and Performance
Higher resolutions require more processing power:
```swift
// Fast export - lower resolution
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.baseline31), width: 1280, height: 720)
.fps(24)
.bitrate(2_000_000),
to: destinationURL,
as: .mp4
)
// Slow export - higher resolution
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 3840, height: 2160)
.fps(60)
.bitrate(25_000_000),
to: destinationURL,
as: .mov
)
```
## Common Video Configurations
### YouTube Upload
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.highAuto), width: 1920, height: 1080)
.fps(30)
.bitrate(8_000_000)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
### Mobile App
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.h264(.mainAuto), width: 1280, height: 720)
.fps(30)
.bitrate(2_500_000)
.color(.sdr),
to: destinationURL,
as: .mp4
)
```
### Archive/Storage
```swift
try await exporter.export(
asset: sourceAsset,
video: .codec(.hevc, width: 1920, height: 1080)
.fps(24)
.bitrate(3_000_000)
.color(.sdr),
to: destinationURL,
as: .mov
)
```
## See Also
- ``VideoOutputSettings`` - Video settings builder
- ``VideoOutputSettings/Codec`` - Supported video codecs
- ``VideoOutputSettings/H264Profile`` - H.264 profile options
- ``VideoOutputSettings/Color`` - Color space configuration
- <doc:AudioConfiguration> - Configuring audio settings
- <doc:CustomSettings> - Using raw video settings dictionaries