mirror of
https://github.com/samsonjs/SJSAssetExportSession.git
synced 2026-03-25 08:45:50 +00:00
Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c9e24d2bc | |||
| 93806c5ed0 |
2 changed files with 41 additions and 52 deletions
|
|
@ -198,6 +198,6 @@ 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].
|
Copyright © 2024-2025 [Sami Samhuri](https://samhuri.net) <sami@samhuri.net>. Released under the terms of the [MIT License][MIT].
|
||||||
|
|
||||||
[MIT]: https://sjs.mit-license.org
|
[MIT]: https://sjs.mit-license.org
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,22 @@ actor SampleWriter {
|
||||||
if let timeRange {
|
if let timeRange {
|
||||||
reader.timeRange = timeRange
|
reader.timeRange = timeRange
|
||||||
}
|
}
|
||||||
|
self.reader = reader
|
||||||
|
|
||||||
let writer = try AVAssetWriter(outputURL: outputURL, fileType: fileType)
|
let writer = try AVAssetWriter(outputURL: outputURL, fileType: fileType)
|
||||||
writer.shouldOptimizeForNetworkUse = optimizeForNetworkUse
|
writer.shouldOptimizeForNetworkUse = optimizeForNetworkUse
|
||||||
writer.metadata = metadata
|
writer.metadata = metadata
|
||||||
|
self.writer = writer
|
||||||
|
|
||||||
|
self.audioOutputSettings = audioOutputSettings
|
||||||
|
self.audioMix = audioMix
|
||||||
|
self.videoOutputSettings = videoOutputSettings
|
||||||
|
self.videoComposition = videoComposition
|
||||||
|
self.timeRange = if let timeRange {
|
||||||
|
timeRange
|
||||||
|
} else {
|
||||||
|
try await CMTimeRange(start: .zero, duration: asset.load(.duration))
|
||||||
|
}
|
||||||
|
|
||||||
// Filter out disabled tracks to avoid problems encoding spatial audio. Ideally this would
|
// Filter out disabled tracks to avoid problems encoding spatial audio. Ideally this would
|
||||||
// preserve track groups and make that all configurable.
|
// preserve track groups and make that all configurable.
|
||||||
|
|
@ -79,30 +92,6 @@ actor SampleWriter {
|
||||||
// Audio is optional so only validate output settings when it's applicable.
|
// Audio is optional so only validate output settings when it's applicable.
|
||||||
if !audioTracks.isEmpty {
|
if !audioTracks.isEmpty {
|
||||||
try Self.validateAudio(outputSettings: audioOutputSettings, writer: writer)
|
try Self.validateAudio(outputSettings: audioOutputSettings, writer: writer)
|
||||||
}
|
|
||||||
let videoTracks = try await asset.loadTracks(withMediaType: .video)
|
|
||||||
.filterAsync { try await $0.load(.isEnabled) }
|
|
||||||
guard !videoTracks.isEmpty else { throw Error.setupFailure(.videoTracksEmpty) }
|
|
||||||
try Self.validateVideo(outputSettings: videoOutputSettings, writer: writer)
|
|
||||||
Self.warnAboutMismatchedVideoSize(
|
|
||||||
renderSize: videoComposition.renderSize,
|
|
||||||
settings: videoOutputSettings
|
|
||||||
)
|
|
||||||
|
|
||||||
self.audioOutputSettings = audioOutputSettings
|
|
||||||
self.audioMix = audioMix
|
|
||||||
self.videoOutputSettings = videoOutputSettings
|
|
||||||
self.videoComposition = videoComposition
|
|
||||||
self.reader = reader
|
|
||||||
self.writer = writer
|
|
||||||
self.timeRange = if let timeRange {
|
|
||||||
timeRange
|
|
||||||
} else {
|
|
||||||
try await CMTimeRange(start: .zero, duration: asset.load(.duration))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This used to be a separate method but that doesn't build in Xcode 26.0 RC
|
|
||||||
if !audioTracks.isEmpty {
|
|
||||||
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
|
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
|
||||||
audioOutput.alwaysCopiesSampleData = false
|
audioOutput.alwaysCopiesSampleData = false
|
||||||
audioOutput.audioMix = audioMix
|
audioOutput.audioMix = audioMix
|
||||||
|
|
@ -121,7 +110,33 @@ actor SampleWriter {
|
||||||
self.audioInput = audioInput
|
self.audioInput = audioInput
|
||||||
}
|
}
|
||||||
|
|
||||||
try await setUpVideo(videoTracks: videoTracks)
|
let videoTracks = try await asset.loadTracks(withMediaType: .video)
|
||||||
|
.filterAsync { try await $0.load(.isEnabled) }
|
||||||
|
guard !videoTracks.isEmpty else { throw Error.setupFailure(.videoTracksEmpty) }
|
||||||
|
try Self.validateVideo(outputSettings: videoOutputSettings, writer: writer)
|
||||||
|
Self.warnAboutMismatchedVideoSize(
|
||||||
|
renderSize: videoComposition.renderSize,
|
||||||
|
settings: videoOutputSettings
|
||||||
|
)
|
||||||
|
let videoOutput = AVAssetReaderVideoCompositionOutput(
|
||||||
|
videoTracks: videoTracks,
|
||||||
|
videoSettings: nil
|
||||||
|
)
|
||||||
|
videoOutput.alwaysCopiesSampleData = false
|
||||||
|
videoOutput.videoComposition = videoComposition
|
||||||
|
guard reader.canAdd(videoOutput) else {
|
||||||
|
throw Error.setupFailure(.cannotAddVideoOutput)
|
||||||
|
}
|
||||||
|
reader.add(videoOutput)
|
||||||
|
self.videoOutput = videoOutput
|
||||||
|
|
||||||
|
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
|
||||||
|
videoInput.expectsMediaDataInRealTime = false
|
||||||
|
guard writer.canAdd(videoInput) else {
|
||||||
|
throw Error.setupFailure(.cannotAddVideoInput)
|
||||||
|
}
|
||||||
|
writer.add(videoInput)
|
||||||
|
self.videoInput = videoInput
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSamples() async throws {
|
func writeSamples() async throws {
|
||||||
|
|
@ -184,32 +199,6 @@ actor SampleWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Setup
|
|
||||||
|
|
||||||
private func setUpVideo(videoTracks: [AVAssetTrack]) throws {
|
|
||||||
precondition(!videoTracks.isEmpty, "Video tracks must be provided")
|
|
||||||
|
|
||||||
let videoOutput = AVAssetReaderVideoCompositionOutput(
|
|
||||||
videoTracks: videoTracks,
|
|
||||||
videoSettings: nil
|
|
||||||
)
|
|
||||||
videoOutput.alwaysCopiesSampleData = false
|
|
||||||
videoOutput.videoComposition = videoComposition
|
|
||||||
guard let reader, reader.canAdd(videoOutput) else {
|
|
||||||
throw Error.setupFailure(.cannotAddVideoOutput)
|
|
||||||
}
|
|
||||||
reader.add(videoOutput)
|
|
||||||
self.videoOutput = videoOutput
|
|
||||||
|
|
||||||
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
|
|
||||||
videoInput.expectsMediaDataInRealTime = false
|
|
||||||
guard let writer, writer.canAdd(videoInput) else {
|
|
||||||
throw Error.setupFailure(.cannotAddVideoInput)
|
|
||||||
}
|
|
||||||
writer.add(videoInput)
|
|
||||||
self.videoInput = videoInput
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Encoding
|
// MARK: - Encoding
|
||||||
|
|
||||||
private func startEncodingAudioTracks() {
|
private func startEncodingAudioTracks() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue