Share ringtone to Garageband via UIActivityViewController
I am implementing an ringtone application and I would like to share the audio file to GarageBand to let users continue the process,
Here is the code on how I share the media
fileprivate func shareMedia(url: URL) {
let activityViewController:UIActivityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = saveAsRingtone
activityViewController.popoverPresentationController?.sourceRect = saveAsRingtone.frame
present(activityViewController, animated: true, completion: nil)
}
To Answer my own question here is the full answer
class AudioUtils {
class func trimAudio(audioToTrim: URL, startTime: Double, setExportSession: (AVAssetExportSession) -> Void, completion: @escaping (URL?, String?) -> Void) {
let audioFileInput = audioToTrim
let mixedAudio: String = "ringtone.aif"
let exportPathDirectory = NSTemporaryDirectory()
// let audioFileDirectory = URL(fileURLWithPath: exportPathDirectory).appendingPathComponent("Project").appendingPathExtension("band")
let projectBandDirectory = exportPathDirectory + "Project.band"
if (FileManager.default.fileExists(atPath: projectBandDirectory)) {
try! FileManager.default.removeItem(at: URL(fileURLWithPath: projectBandDirectory))
}
try! FileManager.default.createDirectory(atPath: projectBandDirectory, withIntermediateDirectories: true, attributes: nil)
let exportPath: String = projectBandDirectory + "/"
let bundlePath = Bundle.main.path(forResource: "projectData", ofType: "")
let fullDestPath = NSURL(fileURLWithPath: exportPath).appendingPathComponent("projectData")
let fullDestPathString = (fullDestPath?.path)!
try! FileManager.default.createDirectory(atPath: exportPath + "Media", withIntermediateDirectories: true, attributes: nil)
try! FileManager.default.createDirectory(atPath: exportPath + "Output", withIntermediateDirectories: true, attributes: nil)
let audioFileOutput = URL(fileURLWithPath: exportPathDirectory + mixedAudio)//.appendingPathExtension("band")
print("Will export to \(audioFileOutput.absoluteString)")
try? FileManager.default.removeItem(at: audioFileOutput)
let asset = AVAsset(url: audioFileInput)
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough)
if (exportSession == nil) {
completion(nil, "ExportSession is nil")
return
}
setExportSession(exportSession!)
let startCMTime = CMTimeMakeWithSeconds(startTime, preferredTimescale: 1)
let stopCMtime = CMTimeMakeWithSeconds(startTime + 30, preferredTimescale: 1)
let exportTimeRange = CMTimeRangeFromTimeToTime(start: startCMTime, end: stopCMtime)
exportSession?.outputURL = audioFileOutput
exportSession?.outputFileType = AVFileType.caf
exportSession?.timeRange = exportTimeRange
exportSession?.exportAsynchronously {
switch (exportSession?.status) {
case .completed:
var options = AKConverter.Options()
// any options left nil will assume the value of the input file
options.format = "aif"
options.sampleRate = 48000
options.bitDepth = 24
let sourceUrl = audioFileOutput
let destUrl = URL(fileURLWithPath: exportPath + "Media/ringtone.aiff")
convertAudio(sourceUrl, outputURL: destUrl)
let fileData = try! Data.init(contentsOf: destUrl)
var fileStream:String = fileData.base64EncodedString(options: NSData.Base64EncodingOptions.init(rawValue: 0))
fileStream = String(fileStream.dropLast())
let playersDictionary = NSMutableDictionary(contentsOfFile: bundlePath!)
let playersNamesArray = (playersDictionary?.object(forKey: "$objects"))! as! NSMutableArray
let nsDataParentDictionary = (playersNamesArray.object(at: 4)) as! NSMutableDictionary
nsDataParentDictionary.setValue(fileStream, forKey: "NS.data")
playersNamesArray.removeObject(at: 4)
playersNamesArray.insert(nsDataParentDictionary, at: 4)
playersDictionary?.setValue(playersNamesArray, forKey: "$objects")
playersDictionary?.write(toFile: fullDestPathString, atomically: true)
do {
try FileManager.default.copyItem(atPath: bundlePath!, toPath: fullDestPathString)
completion(URL(fileURLWithPath: exportPath), nil)
} catch let exception {
completion(nil, exception.localizedDescription)
}
break
default:
completion(nil, exportSession?.error?.localizedDescription)
break
}
}
}
class func convertAudio(_ url: URL, outputURL: URL) {
var error : OSStatus = noErr
var destinationFile : ExtAudioFileRef? = nil
var sourceFile : ExtAudioFileRef? = nil
var srcFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
var dstFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
ExtAudioFileOpenURL(url as CFURL, &sourceFile)
var thePropertySize: UInt32 = UInt32(MemoryLayout.stride(ofValue: srcFormat))
ExtAudioFileGetProperty(sourceFile!,
kExtAudioFileProperty_FileDataFormat,
&thePropertySize, &srcFormat)
dstFormat.mSampleRate = 44100 //Set sample rate
dstFormat.mFormatID = kAudioFormatLinearPCM
dstFormat.mChannelsPerFrame = 1
dstFormat.mBitsPerChannel = 16
dstFormat.mBytesPerPacket = 2 * dstFormat.mChannelsPerFrame
dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame
dstFormat.mFramesPerPacket = 1
dstFormat.mFormatFlags = kAudioFormatFlagIsBigEndian |
kAudioFormatFlagIsSignedInteger
// Create destination file
error = ExtAudioFileCreateWithURL(
outputURL as CFURL,
kAudioFileAIFFType,
&dstFormat,
nil,
AudioFileFlags.eraseFile.rawValue,
&destinationFile)
print("Error = \(error)")
error = ExtAudioFileSetProperty(sourceFile!,
kExtAudioFileProperty_ClientDataFormat,
thePropertySize,
&dstFormat)
print("Error = \(error)")
error = ExtAudioFileSetProperty(destinationFile!,
kExtAudioFileProperty_ClientDataFormat,
thePropertySize,
&dstFormat)
print("Error = \(error)")
let bufferByteSize : UInt32 = 32768
var srcBuffer = [UInt8](repeating: 0, count: 32768)
var sourceFrameOffset : ULONG = 0
while(true){
var fillBufList = AudioBufferList(
mNumberBuffers: 1,
mBuffers: AudioBuffer(
mNumberChannels: 2,
mDataByteSize: UInt32(srcBuffer.count),
mData: &srcBuffer
)
)
var numFrames : UInt32 = 0
if(dstFormat.mBytesPerFrame > 0){
numFrames = bufferByteSize / dstFormat.mBytesPerFrame
}
error = ExtAudioFileRead(sourceFile!, &numFrames, &fillBufList)
print("Error = \(error)")
if(numFrames == 0){
error = noErr;
break;
}
sourceFrameOffset += numFrames
error = ExtAudioFileWrite(destinationFile!, numFrames, &fillBufList)
print("Error = \(error)")
}
error = ExtAudioFileDispose(destinationFile!)
print("Error = \(error)")
error = ExtAudioFileDispose(sourceFile!)
print("Error = \(error)")
}
}
What happens here?... First of all here is a function to trim the audio for 30 seconds
I have checked an existing .band project file and I got its components, and tried to create these files programmatically. Please feel free to ask me anything about the code
There is projectData file that you should use, this is a file found in every .garageBand folder https://www.dropbox.com/s/7r7uh2ekzigyy3u/projectData?dl=0
Solution 1:
There's no problem with this method for sharing a track on Garageband
Just Make sure that your shared file Extension is "band"
.appendingPathExtension("band")
not anything else