react-native-compressor 1.8.15 → 1.8.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +36 -9
  2. package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +58 -27
  3. package/android/src/main/java/com/reactnativecompressor/Audio/AudioHelper.kt +49 -0
  4. package/android/src/main/java/com/reactnativecompressor/Audio/AudioMain.kt +2 -6
  5. package/android/src/main/java/com/reactnativecompressor/Utils/createVideoThumbnail.kt +0 -2
  6. package/android/src/main/java/com/reactnativecompressor/Video/VideoMain.kt +6 -6
  7. package/ios/Audio/AudioHelper.swift +49 -0
  8. package/ios/Audio/AudioMain.swift +46 -82
  9. package/ios/Audio/AudioOptions.swift +37 -0
  10. package/ios/Audio/FormatConverter/FormatConverter+Compressed.swift +326 -0
  11. package/ios/Audio/FormatConverter/FormatConverter+PCM.swift +239 -0
  12. package/ios/Audio/FormatConverter/FormatConverter+Utilities.swift +103 -0
  13. package/ios/Audio/FormatConverter/FormatConverter.swift +264 -0
  14. package/ios/Compressor.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  15. package/ios/Video/NextLevelSessionExporter.swift +694 -0
  16. package/ios/Video/VideoMain.swift +9 -8
  17. package/lib/commonjs/Audio/index.js +1 -3
  18. package/lib/commonjs/Audio/index.js.map +1 -1
  19. package/lib/commonjs/utils/index.js +0 -2
  20. package/lib/commonjs/utils/index.js.map +1 -1
  21. package/lib/module/Audio/index.js +1 -3
  22. package/lib/module/Audio/index.js.map +1 -1
  23. package/lib/module/utils/index.js +0 -3
  24. package/lib/module/utils/index.js.map +1 -1
  25. package/lib/typescript/Audio/index.d.ts.map +1 -1
  26. package/lib/typescript/index.d.ts +7 -1
  27. package/lib/typescript/index.d.ts.map +1 -1
  28. package/lib/typescript/utils/index.d.ts +11 -2
  29. package/lib/typescript/utils/index.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/react-native-compressor.podspec +0 -1
  32. package/src/Audio/index.tsx +1 -3
  33. package/src/utils/index.tsx +12 -3
@@ -0,0 +1,326 @@
1
+ // Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
2
+
3
+ import AVFoundation
4
+
5
+ // MARK: - internal helper functions
6
+
7
+
8
+ public extension AVURLAsset {
9
+ /// Audio format for the file in the URL asset
10
+ var audioFormat: AVAudioFormat? {
11
+ // pull the input format out of the audio file...
12
+ if let source = try? AVAudioFile(forReading: url) {
13
+ return source.fileFormat
14
+
15
+ // if that fails it might be a video, so check the tracks for audio
16
+ } else {
17
+ let audioTracks = tracks.filter { $0.mediaType == .audio }
18
+
19
+ guard !audioTracks.isEmpty else { return nil }
20
+
21
+ let formatDescriptions = audioTracks.compactMap {
22
+ $0.formatDescriptions as? [CMFormatDescription]
23
+ }.reduce([], +)
24
+
25
+ let audioFormats: [AVAudioFormat] = formatDescriptions.compactMap {
26
+ AVAudioFormat(cmAudioFormatDescription: $0)
27
+ }
28
+ return audioFormats.first
29
+ }
30
+ }
31
+ }
32
+
33
+
34
+ extension FormatConverter {
35
+ /// Example of the most simplistic AVFoundation conversion.
36
+ /// With this approach you can't really specify any settings other than the limited presets.
37
+ /// No sample rate conversion in this. This isn't used in the public methods but is here
38
+ /// for example.
39
+ ///
40
+ /// see `AVAssetExportSession`:
41
+ /// *Prior to initializing an instance of AVAssetExportSession, you can invoke
42
+ /// +allExportPresets to obtain the complete list of presets available. Use
43
+ /// +exportPresetsCompatibleWithAsset: to obtain a list of presets that are compatible
44
+ /// with a specific AVAsset.*
45
+ ///
46
+ /// This is no longer used in this class as it's not possible to convert sample rate or other
47
+ /// required options. It will use the next function instead
48
+ func convertCompressed(presetName: String, completionHandler: FormatConverterCallback? = nil) {
49
+ guard let inputURL = inputURL else {
50
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
51
+ return
52
+ }
53
+ guard let outputURL = outputURL else {
54
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
55
+ return
56
+ }
57
+
58
+ let asset = AVURLAsset(url: inputURL)
59
+ guard let session = AVAssetExportSession(asset: asset,
60
+ presetName: presetName) else { return }
61
+
62
+ session.determineCompatibleFileTypes { list in
63
+
64
+ guard let outputFileType: AVFileType = list.first else {
65
+ let error = Self.createError(message: "Unable to determine a compatible file type from \(inputURL.path)")
66
+ completionHandler?(error)
67
+ return
68
+ }
69
+
70
+ // session.progress could be sent out via a delegate for this session
71
+ session.outputURL = outputURL
72
+ session.outputFileType = outputFileType
73
+ session.exportAsynchronously {
74
+ completionHandler?(session.error)
75
+ }
76
+ }
77
+ }
78
+
79
+ /// Convert to compressed first creating a tmp file to PCM to allow more flexible conversion
80
+ /// options to work.
81
+ func convertCompressed(completionHandler: FormatConverterCallback? = nil) {
82
+ guard let inputURL = inputURL else {
83
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
84
+ return
85
+ }
86
+ guard let outputURL = outputURL else {
87
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
88
+ return
89
+ }
90
+
91
+ guard let options = options else {
92
+ completionHandler?(Self.createError(message: "Options can't be nil."))
93
+ return
94
+ }
95
+
96
+ let tempName = outputURL.deletingPathExtension().lastPathComponent + "_TEMP.wav"
97
+ let tempFile = outputURL.deletingLastPathComponent().appendingPathComponent(tempName)
98
+
99
+ var tempOptions = FormatConverter.Options()
100
+ tempOptions.bitDepthRule = .lessThanOrEqual
101
+ tempOptions.bitDepth = 24
102
+ tempOptions.sampleRate = options.sampleRate
103
+ tempOptions.channels = options.channels
104
+ tempOptions.format = .wav
105
+
106
+ let tempConverter = FormatConverter(inputURL: inputURL,
107
+ outputURL: tempFile,
108
+ options: tempOptions)
109
+
110
+ tempConverter.start { error in
111
+ if let error = error {
112
+ completionHandler?(Self.createError(message: "Failed to convert input to PCM: \(error.localizedDescription)"))
113
+ return
114
+ }
115
+
116
+ self.inputURL = tempFile
117
+
118
+ self.convertPCMToCompressed { error in
119
+ try? FileManager.default.removeItem(at: tempFile)
120
+ completionHandler?(error)
121
+ }
122
+ }
123
+ }
124
+
125
+ /// The AVFoundation way. *This doesn't currently handle compressed input - only compressed output.*
126
+ func convertPCMToCompressed(completionHandler: FormatConverterCallback? = nil) {
127
+ guard let inputURL = inputURL else {
128
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
129
+ return
130
+ }
131
+ guard let outputURL = outputURL else {
132
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
133
+ return
134
+ }
135
+
136
+ guard let options = options, let outputFormat = options.format else {
137
+ completionHandler?(Self.createError(message: "Options can't be nil."))
138
+ return
139
+ }
140
+
141
+ // verify outputFormat
142
+ guard FormatConverter.outputFormats.contains(outputFormat) else {
143
+ completionHandler?(Self.createError(message: "The output file format isn't able to be produced by this class."))
144
+ return
145
+ }
146
+
147
+ let asset = AVURLAsset(url: inputURL)
148
+ do {
149
+ self.reader = try AVAssetReader(asset: asset)
150
+
151
+ } catch let err as NSError {
152
+ completionHandler?(err)
153
+ return
154
+ }
155
+
156
+ guard let reader = reader else {
157
+ completionHandler?(Self.createError(message: "Unable to setup the AVAssetReader."))
158
+ return
159
+ }
160
+
161
+ guard let inputFormat = asset.audioFormat else {
162
+ completionHandler?(Self.createError(message: "Unable to read the input file format."))
163
+ return
164
+ }
165
+
166
+ var format: AVFileType
167
+ var formatKey: AudioFormatID
168
+
169
+ switch outputFormat {
170
+ case .m4a, .mp4:
171
+ format = .m4a
172
+ formatKey = kAudioFormatMPEG4AAC
173
+ case .aif:
174
+ format = .aiff
175
+ formatKey = kAudioFormatLinearPCM
176
+ case .caf:
177
+ format = .caf
178
+ formatKey = kAudioFormatLinearPCM
179
+ case .wav:
180
+ format = .wav
181
+ formatKey = kAudioFormatLinearPCM
182
+ default:
183
+ print("Unsupported output format: \(outputFormat)")
184
+ return
185
+ }
186
+
187
+ do {
188
+ self.writer = try AVAssetWriter(outputURL: outputURL, fileType: format)
189
+ } catch let err as NSError {
190
+ completionHandler?(err)
191
+ return
192
+ }
193
+
194
+ guard let writer = writer else {
195
+ completionHandler?(Self.createError(message: "Unable to setup the AVAssetWriter."))
196
+ return
197
+ }
198
+
199
+ // 1. chosen option. 2. same as input file. 3. 16 bit
200
+ // optional in case of compressed audio. That said, the other conversion methods are actually used in
201
+ // that case
202
+ let bitDepth = (options.bitDepth ?? inputFormat.settings[AVLinearPCMBitDepthKey] ?? 16) as Any
203
+ var isFloat = false
204
+ if let intDepth = bitDepth as? Int {
205
+ isFloat = intDepth == 32
206
+ }
207
+
208
+ var sampleRate = options.sampleRate ?? inputFormat.sampleRate
209
+ let channels = options.channels ?? inputFormat.channelCount
210
+
211
+ if sampleRate == 0 {
212
+ print("Sample rate can't be 0 - assigning to default format of 48k. inputFormat is", inputFormat)
213
+ sampleRate = 48000
214
+ }
215
+ var outputSettings: [String: Any]?
216
+
217
+ // Note: AVAssetReaderOutput does not currently support compressed audio
218
+ if formatKey == kAudioFormatMPEG4AAC {
219
+ if sampleRate > 48000 {
220
+ sampleRate = 48000
221
+ }
222
+ // mono should be 1/2 the shown bitrate
223
+ let perChannel = channels == 1 ? 2 : 1
224
+
225
+ // reset these for m4a:
226
+ outputSettings = [
227
+ AVFormatIDKey: formatKey,
228
+ AVSampleRateKey: sampleRate,
229
+ AVNumberOfChannelsKey: channels,
230
+ AVEncoderBitRateKey: Int(options.bitRate) / perChannel,
231
+ AVEncoderBitRateStrategyKey: AVAudioBitRateStrategy_Constant,
232
+ ]
233
+ } else {
234
+ outputSettings = [
235
+ AVFormatIDKey: formatKey,
236
+ AVSampleRateKey: sampleRate,
237
+ AVNumberOfChannelsKey: channels,
238
+ AVLinearPCMBitDepthKey: bitDepth,
239
+ AVLinearPCMIsFloatKey: isFloat,
240
+ AVLinearPCMIsBigEndianKey: format != .wav,
241
+ AVLinearPCMIsNonInterleaved: !(options.isInterleaved ?? inputFormat.isInterleaved),
242
+ ]
243
+ }
244
+
245
+ let hint = asset.audioFormat?.formatDescription
246
+
247
+ let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings, sourceFormatHint: hint)
248
+ writer.add(writerInput)
249
+
250
+ guard let track = asset.tracks(withMediaType: .audio).first else {
251
+ completionProxy(error: Self.createError(message: "No audio was found in the input file."),
252
+ completionHandler: completionHandler)
253
+ return
254
+ }
255
+
256
+ let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: nil)
257
+ guard reader.canAdd(readerOutput) else {
258
+ completionProxy(error: Self.createError(message: "Unable to add reader output."),
259
+ completionHandler: completionHandler)
260
+ return
261
+ }
262
+ reader.add(readerOutput)
263
+
264
+ if !writer.startWriting() {
265
+ print("Failed to start writing. Error:", writer.error?.localizedDescription)
266
+ completionProxy(error: writer.error,
267
+ completionHandler: completionHandler)
268
+ return
269
+ }
270
+
271
+ writer.startSession(atSourceTime: CMTime.zero)
272
+
273
+ if !reader.startReading() {
274
+ print("Failed to start reading. Error:", reader.error?.localizedDescription)
275
+ completionProxy(error: reader.error,
276
+ completionHandler: completionHandler)
277
+ return
278
+ }
279
+
280
+ let queue = DispatchQueue(label: "com.audiodesigndesk.ADD.FormatConverter.convertAsset")
281
+
282
+ // session.progress could be sent out via a delegate for this session
283
+ writerInput.requestMediaDataWhenReady(on: queue, using: {
284
+ var processing = true // safety flag to prevent runaway loops if errors
285
+
286
+ while writerInput.isReadyForMoreMediaData, processing {
287
+ if reader.status == .reading,
288
+ let buffer = readerOutput.copyNextSampleBuffer()
289
+ {
290
+ writerInput.append(buffer)
291
+
292
+ } else {
293
+ writerInput.markAsFinished()
294
+
295
+ switch reader.status {
296
+ case .failed:
297
+ print("Conversion failed with error", reader.error)
298
+ writer.cancelWriting()
299
+ self.completionProxy(error: reader.error, completionHandler: completionHandler)
300
+ case .cancelled:
301
+ print("Conversion cancelled")
302
+ self.completionProxy(error: Self.createError(message: "Process canceled"),
303
+ completionHandler: completionHandler)
304
+ case .completed:
305
+ writer.finishWriting {
306
+ switch writer.status {
307
+ case .failed:
308
+ print("Conversion failed at finishWriting")
309
+ self.completionProxy(error: writer.error,
310
+ completionHandler: completionHandler)
311
+ default:
312
+ // no errors
313
+ completionHandler?(nil)
314
+ }
315
+ }
316
+ default:
317
+ break
318
+ }
319
+ processing = false
320
+ }
321
+ }
322
+ }) // requestMediaDataWhenReady
323
+ }
324
+ }
325
+
326
+
@@ -0,0 +1,239 @@
1
+ // Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
2
+
3
+ import AVFoundation
4
+
5
+ // MARK: - internal helper functions
6
+
7
+ extension FormatConverter {
8
+ func convertToPCM(completionHandler: FormatConverterCallback? = nil) {
9
+ guard let inputURL = inputURL else {
10
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
11
+ return
12
+ }
13
+ guard let outputURL = outputURL else {
14
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
15
+ return
16
+ }
17
+
18
+ guard let options = options, let outputFormat = options.format else {
19
+ completionHandler?(Self.createError(message: "Options can't be nil."))
20
+ return
21
+ }
22
+ var format: AudioFileTypeID
23
+
24
+ switch outputFormat {
25
+ case .aif:
26
+ format = kAudioFileAIFFType
27
+ case .wav:
28
+ format = kAudioFileWAVEType
29
+ case .caf:
30
+ format = kAudioFileCAFType
31
+ default:
32
+ completionHandler?(Self.createError(message: "Output file must be caf, wav or aif."))
33
+ return
34
+ }
35
+
36
+ var inputFile: ExtAudioFileRef?
37
+ var outputFile: ExtAudioFileRef?
38
+
39
+ func closeFiles() {
40
+ if let strongFile = inputFile {
41
+ if noErr != ExtAudioFileDispose(strongFile) {
42
+ print("Error disposing input file, could have a memory leak")
43
+ }
44
+ }
45
+ inputFile = nil
46
+
47
+ if let strongFile = outputFile {
48
+ if noErr != ExtAudioFileDispose(strongFile) {
49
+ print("Error disposing output file, could have a memory leak")
50
+ }
51
+ }
52
+ outputFile = nil
53
+ }
54
+
55
+ // make sure these are closed on any exit to avoid leaking the file objects
56
+ defer {
57
+ closeFiles()
58
+ }
59
+
60
+ if noErr != ExtAudioFileOpenURL(inputURL as CFURL, &inputFile) {
61
+ completionHandler?(Self.createError(message: "Unable to open the input file."))
62
+ return
63
+ }
64
+
65
+ guard let strongInputFile = inputFile else {
66
+ completionHandler?(Self.createError(message: "Unable to open the input file."))
67
+ return
68
+ }
69
+
70
+ var inputDescription = AudioStreamBasicDescription()
71
+ var inputDescriptionSize = UInt32(MemoryLayout.stride(ofValue: inputDescription))
72
+
73
+ if noErr != ExtAudioFileGetProperty(strongInputFile,
74
+ kExtAudioFileProperty_FileDataFormat,
75
+ &inputDescriptionSize,
76
+ &inputDescription)
77
+ {
78
+ completionHandler?(Self.createError(message: "Unable to get the input file data format."))
79
+ return
80
+ }
81
+
82
+ var outputDescription = createOutputDescription(options: options,
83
+ outputFormatID: format,
84
+ inputDescription: inputDescription)
85
+
86
+ let inputFormat = AudioFileFormat(rawValue: inputURL.pathExtension.lowercased()) ?? .unknown
87
+
88
+ guard inputFormat != outputFormat ||
89
+ outputDescription.mSampleRate != inputDescription.mSampleRate ||
90
+ outputDescription.mChannelsPerFrame != inputDescription.mChannelsPerFrame ||
91
+ outputDescription.mBitsPerChannel != inputDescription.mBitsPerChannel
92
+ else {
93
+ print("No conversion is needed, formats are the same. Copying to", outputURL)
94
+ // just copy it?
95
+ do {
96
+ try FileManager.default.copyItem(at: inputURL, to: outputURL)
97
+ completionHandler?(nil)
98
+ } catch let err as NSError {
99
+ print(err)
100
+ }
101
+ return
102
+ }
103
+
104
+ // Create destination file
105
+ if noErr != ExtAudioFileCreateWithURL(outputURL as CFURL,
106
+ format,
107
+ &outputDescription,
108
+ nil,
109
+ AudioFileFlags.eraseFile.rawValue, // overwrite old file if present
110
+ &outputFile)
111
+ {
112
+ completionProxy(error: Self.createError(message: "Unable to create output file at \(outputURL.path). " +
113
+ "dstFormat \(outputDescription)"),
114
+ completionHandler: completionHandler)
115
+ return
116
+ }
117
+
118
+ guard let strongOutputFile = outputFile else {
119
+ completionProxy(error: Self.createError(message: "Output file is nil."),
120
+ completionHandler: completionHandler)
121
+ return
122
+ }
123
+
124
+ // The format must be linear PCM (kAudioFormatLinearPCM).
125
+ // You must set this in order to encode or decode a non-PCM file data format.
126
+ // You may set this on PCM files to specify the data format used in your calls
127
+ // to read/write.
128
+ if noErr != ExtAudioFileSetProperty(strongInputFile,
129
+ kExtAudioFileProperty_ClientDataFormat,
130
+ inputDescriptionSize,
131
+ &outputDescription)
132
+ {
133
+ completionProxy(error: Self.createError(message: "Unable to set data format on input file."),
134
+ completionHandler: completionHandler)
135
+ return
136
+ }
137
+
138
+ if noErr != ExtAudioFileSetProperty(strongOutputFile,
139
+ kExtAudioFileProperty_ClientDataFormat,
140
+ inputDescriptionSize,
141
+ &outputDescription)
142
+ {
143
+ completionProxy(error: Self.createError(message: "Unable to set the output file data format."),
144
+ completionHandler: completionHandler)
145
+ return
146
+ }
147
+ let bufferByteSize: UInt32 = 32768
148
+ var srcBuffer = [UInt8](repeating: 0, count: Int(bufferByteSize))
149
+ var sourceFrameOffset: UInt32 = 0
150
+
151
+ srcBuffer.withUnsafeMutableBytes { body in
152
+ while true {
153
+ let mBuffer = AudioBuffer(mNumberChannels: inputDescription.mChannelsPerFrame,
154
+ mDataByteSize: bufferByteSize,
155
+ mData: body.baseAddress)
156
+
157
+ var fillBufList = AudioBufferList(mNumberBuffers: 1,
158
+ mBuffers: mBuffer)
159
+ var frameCount: UInt32 = 0
160
+
161
+ if outputDescription.mBytesPerFrame > 0 {
162
+ frameCount = bufferByteSize / outputDescription.mBytesPerFrame
163
+ }
164
+
165
+ if noErr != ExtAudioFileRead(strongInputFile,
166
+ &frameCount,
167
+ &fillBufList)
168
+ {
169
+ completionProxy(error: Self.createError(message: "Error reading from the input file."),
170
+ completionHandler: completionHandler)
171
+ return
172
+ }
173
+ // EOF
174
+ if frameCount == 0 { break }
175
+
176
+ sourceFrameOffset += frameCount
177
+
178
+ if noErr != ExtAudioFileWrite(strongOutputFile,
179
+ frameCount,
180
+ &fillBufList)
181
+ {
182
+ completionProxy(error: Self.createError(message: "Error reading from the output file."),
183
+ completionHandler: completionHandler)
184
+ return
185
+ }
186
+ }
187
+ }
188
+
189
+ closeFiles()
190
+
191
+ // no errors
192
+ completionHandler?(nil)
193
+ }
194
+
195
+ func createOutputDescription(options: Options,
196
+ outputFormatID: AudioFormatID,
197
+ inputDescription: AudioStreamBasicDescription) -> AudioStreamBasicDescription
198
+ {
199
+ let mFormatID: AudioFormatID = kAudioFormatLinearPCM
200
+
201
+ let mSampleRate = options.sampleRate ?? inputDescription.mSampleRate
202
+ let mChannelsPerFrame = options.channels ?? inputDescription.mChannelsPerFrame
203
+ var mBitsPerChannel = options.bitDepth ?? inputDescription.mBitsPerChannel
204
+
205
+ // For example: don't allow upsampling to 24bit if the src is 16
206
+ if options.bitDepthRule == .lessThanOrEqual, mBitsPerChannel > inputDescription.mBitsPerChannel {
207
+ mBitsPerChannel = inputDescription.mBitsPerChannel
208
+ }
209
+
210
+ var mBytesPerFrame = mBitsPerChannel * mChannelsPerFrame / 8
211
+ var mBytesPerPacket = options.bitDepth == nil ? inputDescription.mBytesPerPacket : mBytesPerFrame
212
+
213
+ if mBitsPerChannel == 0 {
214
+ mBitsPerChannel = 16
215
+ mBytesPerPacket = 2 * mChannelsPerFrame
216
+ mBytesPerFrame = 2 * mChannelsPerFrame
217
+ }
218
+
219
+ var mFormatFlags: AudioFormatFlags = kLinearPCMFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger
220
+ if outputFormatID == kAudioFileAIFFType {
221
+ mFormatFlags = mFormatFlags | kLinearPCMFormatFlagIsBigEndian
222
+ }
223
+
224
+ if outputFormatID == kAudioFileWAVEType, mBitsPerChannel == 8 {
225
+ // if is 8 BIT PER CHANNEL, remove kAudioFormatFlagIsSignedInteger
226
+ mFormatFlags &= ~kAudioFormatFlagIsSignedInteger
227
+ }
228
+
229
+ return AudioStreamBasicDescription(mSampleRate: mSampleRate,
230
+ mFormatID: mFormatID,
231
+ mFormatFlags: mFormatFlags,
232
+ mBytesPerPacket: mBytesPerPacket,
233
+ mFramesPerPacket: 1,
234
+ mBytesPerFrame: mBytesPerFrame,
235
+ mChannelsPerFrame: mChannelsPerFrame,
236
+ mBitsPerChannel: mBitsPerChannel,
237
+ mReserved: 0)
238
+ }
239
+ }
@@ -0,0 +1,103 @@
1
+ // Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
2
+
3
+ import AVFoundation
4
+
5
+ extension FormatConverter {
6
+ class func createError(message: String, code: Int = 1) -> NSError {
7
+ let userInfo = [NSLocalizedDescriptionKey: message]
8
+ return NSError(domain: "com.audiodesigndesk.FormatConverter.error",
9
+ code: code,
10
+ userInfo: userInfo)
11
+ }
12
+ }
13
+
14
+ public extension FormatConverter {
15
+ /// Is this file a PCM file?
16
+ /// - Parameters:
17
+ /// - url: The URL to parse
18
+ /// - ignorePathExtension: Do a deep parse rather than rely on the path extension
19
+ /// - Returns: Bool or nil if it couldn't be determined
20
+ class func isPCM(url: URL, ignorePathExtension _: Bool = false) -> Bool? {
21
+ guard let value = isCompressed(url: url) else { return nil }
22
+ return !value
23
+ }
24
+
25
+ /// Compressed format or not
26
+ class func isCompressed(url: URL, ignorePathExtension: Bool = false) -> Bool? {
27
+ guard !ignorePathExtension else {
28
+ return isCompressedExt(url: url)
29
+ }
30
+ let ext = url.pathExtension.lowercased()
31
+
32
+ switch ext {
33
+ case "wav", "bwf", "aif", "aiff", "caf":
34
+ return false
35
+
36
+ case "m4a", "mp3", "mp4", "m4v", "mpg", "flac", "ogg":
37
+ return true
38
+
39
+ default:
40
+ // if the file extension is missing or unknown, open the file and check it
41
+ return isCompressedExt(url: url) ?? false
42
+ }
43
+ }
44
+
45
+ private class func isCompressedExt(url: URL) -> Bool? {
46
+ var inputFile: ExtAudioFileRef?
47
+
48
+ func closeFiles() {
49
+ if let strongFile = inputFile {
50
+ // print("🗑 Disposing input", inputURL.path)
51
+ if noErr != ExtAudioFileDispose(strongFile) {
52
+ print("Error disposing input file, could have a memory leak")
53
+ }
54
+ }
55
+ inputFile = nil
56
+ }
57
+
58
+ // make sure these are closed on any exit
59
+ defer {
60
+ closeFiles()
61
+ }
62
+
63
+ if noErr != ExtAudioFileOpenURL(url as CFURL,
64
+ &inputFile)
65
+ {
66
+ print("Unable to open", url.lastPathComponent)
67
+ return nil
68
+ }
69
+
70
+ guard let strongInputFile = inputFile else {
71
+ return nil
72
+ }
73
+
74
+ var inputDescription = AudioStreamBasicDescription()
75
+ var inputDescriptionSize = UInt32(MemoryLayout.stride(ofValue: inputDescription))
76
+
77
+ if noErr != ExtAudioFileGetProperty(strongInputFile,
78
+ kExtAudioFileProperty_FileDataFormat,
79
+ &inputDescriptionSize,
80
+ &inputDescription) {}
81
+
82
+ let mFormatID = inputDescription.mFormatID
83
+
84
+ switch mFormatID {
85
+ case kAudioFormatLinearPCM,
86
+ kAudioFormatAppleLossless: return false
87
+ default:
88
+ // basically all other format IDs are compressed
89
+ return true
90
+ }
91
+ }
92
+ }
93
+
94
+ extension Comparable {
95
+ // ie: 5.clamped(to: 7...10)
96
+ // ie: 5.0.clamped(to: 7.0...10.0)
97
+ // ie: "a".clamped(to: "b"..."h")
98
+ /// **OTCore:**
99
+ /// Returns the value clamped to the passed range.
100
+ @inlinable func clamped(to limits: ClosedRange<Self>) -> Self {
101
+ min(max(self, limits.lowerBound), limits.upperBound)
102
+ }
103
+ }