synthesizeToFile does not use provided path
🚀 Feature Requests
it'd be nice if synthesizeToFile would somehow accept a full path to file (or maybe new method could be added to solve this) - otherwise some default directory is used as root and when the "fileName" is provided as e.g. /data/user/0/net.hydralien.pidging/app_flutter/9accce7af07e27e86bb47b7b3d69f6d2c094c1fb.audio, the actual file is attempted to be stored as /storage/emulated/0/Android/data/net.hydralien.pidging/files/data/user/0/net.hydralien.pidging/app_flutter/9accce7af07e27e86bb47b7b3d69f6d2c094c1fb.audio, failing with java.io.FileNotFoundException: open failed: ENOENT (No such file or directory)
Describe the feature
Because Android TTS does not work offline if the requested language is not installed in the system, I'm trying to store the synthesised files offline so they could be played if exist. However it's not clear how to get to them once they're generated (I found some answer to that here: https://stackoverflow.com/questions/65901746/how-to-get-the-absolute-path-of-a-file-in-flutter/65904131 - but that's not quite obvious, and I'm not sure whether it's reliable)
Platforms affected (mark all that apply)
- [x] :robot: Android
Thanks for your job. May be kotlin function synthesizeToFile could become :
` private fun synthesizeToFile(text: String, fileName: String) { val fullPath: String val uuid: String = UUID.randomUUID().toString() bundle!!.putString( TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, SYNTHESIZE_TO_FILE_PREFIX + uuid )
val result: Int =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !fileName.startsWith("/")) {
val resolver = this.context?.contentResolver
val contentValues =
ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav")
put(
MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_MUSIC
)
}
val uri =
resolver?.insert(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
contentValues
)
this.parcelFileDescriptor = resolver?.openFileDescriptor(uri!!, "rw")
fullPath = uri?.path + File.separatorChar + fileName
tts!!.synthesizeToFile(
text,
bundle!!,
parcelFileDescriptor!!,
SYNTHESIZE_TO_FILE_PREFIX + uuid
)
} else {
val musicDir =
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MUSIC
)
val file =
if (fileName.startsWith("/")) File(fileName)
else File(musicDir, fileName)
fullPath = file.path
tts!!.synthesizeToFile(text, bundle!!, file!!, SYNTHESIZE_TO_FILE_PREFIX + uuid)
}
if (result == TextToSpeech.SUCCESS) {
Log.d(tag, "Successfully created file : $fullPath")
} else {
Log.d(tag, "Failed creating file : $fullPath")
}
}
`
@Nimo11 that's a good idea and I'll see if I can implement that. I'll also have to check on how to accomplish the same on iOS.
Yes that would be very interesting for me thanks
You can try this in SwiftFlutterTtsPlugin. It work for me :
private func synthesizeToFile(text: String, fileName: String, result: @escaping FlutterResult) {
var output: AVAudioFile?
var failed = false
let utterance = AVSpeechUtterance(string: text)
if self.voice != nil {
utterance.voice = self.voice!
} else {
utterance.voice = AVSpeechSynthesisVoice(language: self.language)
}
utterance.rate = self.rate
utterance.volume = self.volume
utterance.pitchMultiplier = self.pitch
if #available(iOS 13.0, *) {
self.synthesizer.write(utterance) { (buffer: AVAudioBuffer) in
guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
NSLog("unknow buffer type: \(buffer)")
failed = true
return
}
print(pcmBuffer.format)
if pcmBuffer.frameLength == 0 {
// finished
} else {
// append buffer to file
let fileURL = fileName.hasPrefix("/") ?
URL(string: fileName):
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(fileName)
if let fileURL {
NSLog("Saving utterance to file: \(fileURL.absoluteString)")
if output == nil {
do {
if #available(iOS 17.0, *) {
guard let audioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: Double(22050), channels: 1, interleaved: false) else {
NSLog("Error creating audio format for iOS 17+")
failed = true
return
}
output = try AVAudioFile(forWriting: fileURL, settings: audioFormat.settings)
} else {
output = try AVAudioFile(forWriting: fileURL, settings: pcmBuffer.format.settings, commonFormat: .pcmFormatInt16, interleaved: false)
}
} catch {
NSLog("Error creating AVAudioFile: \(error.localizedDescription)")
failed = true
return
}
}
try! output!.write(from: pcmBuffer)
}}
}
} else {
result("Unsupported iOS version")
}
if failed {
result(0)
}
if self.awaitSynthCompletion {
self.synthResult = result
} else {
result(1)
}
}