diff --git a/Package.swift b/Package.swift index 56c1274..cc28e51 100644 --- a/Package.swift +++ b/Package.swift @@ -23,6 +23,7 @@ let package = Package( .process("Resources/test-720p-h264-24fps.mov"), .process("Resources/test-no-audio.mp4"), .process("Resources/test-no-video.m4a"), + .process("Resources/test-spatial-audio.mov"), ] ), ] diff --git a/Sources/SJSAssetExportSession/Array+Extensions.swift b/Sources/SJSAssetExportSession/Array+Extensions.swift new file mode 100644 index 0000000..5201399 --- /dev/null +++ b/Sources/SJSAssetExportSession/Array+Extensions.swift @@ -0,0 +1,18 @@ +// +// Array+Extensions.swift +// SJSAssetExportSession +// +// Created by Sami Samhuri on 2024-10-04. +// + +extension Array { + func filterAsync(_ isIncluded: (Element) async throws -> Bool) async rethrows -> [Element] { + var result: [Element] = [] + for element in self { + if try await isIncluded(element) { + result.append(element) + } + } + return result + } +} diff --git a/Sources/SJSAssetExportSession/SampleWriter.swift b/Sources/SJSAssetExportSession/SampleWriter.swift index 6922cdd..32095fe 100644 --- a/Sources/SJSAssetExportSession/SampleWriter.swift +++ b/Sources/SJSAssetExportSession/SampleWriter.swift @@ -70,12 +70,16 @@ actor SampleWriter { writer.shouldOptimizeForNetworkUse = optimizeForNetworkUse writer.metadata = metadata + // Filter out disabled tracks to avoid problems encoding spatial audio. Ideally this would + // preserve track groups and make that all configurable. let audioTracks = try await asset.loadTracks(withMediaType: .audio) + .filterAsync { try await $0.load(.isEnabled) } // Audio is optional so only validate output settings when it's applicable. if !audioTracks.isEmpty { 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( diff --git a/Tests/SJSAssetExportSessionTests/Resources/test-spatial-audio.mov b/Tests/SJSAssetExportSessionTests/Resources/test-spatial-audio.mov new file mode 100644 index 0000000..541411c Binary files /dev/null and b/Tests/SJSAssetExportSessionTests/Resources/test-spatial-audio.mov differ diff --git a/Tests/SJSAssetExportSessionTests/SJSAssetExportSessionTests.swift b/Tests/SJSAssetExportSessionTests/SJSAssetExportSessionTests.swift index 865d97e..bd2ac61 100644 --- a/Tests/SJSAssetExportSessionTests/SJSAssetExportSessionTests.swift +++ b/Tests/SJSAssetExportSessionTests/SJSAssetExportSessionTests.swift @@ -357,4 +357,21 @@ final class ExportSessionTests { })?.load(.value) as? NSString #expect(commonMetadataValue == "+48.50176+123.34368/") } + + @Test func test_works_with_spatial_audio_track() async throws { + let sourceURL = resourceURL(named: "test-spatial-audio.mov") + let destinationURL = makeTemporaryURL() + + let subject = ExportSession() + try await subject.export( + asset: makeAsset(url: sourceURL), + video: .codec(.h264, size: CGSize(width: 720, height: 1280)), + to: destinationURL.url, + as: .mp4 + ) + + let exportedAsset = AVURLAsset(url: destinationURL.url) + let audioTracks = try await exportedAsset.loadTracks(withMediaType: .audio) + #expect(audioTracks.count == 1) + } }