1

Is anyone aware of a TTS system for Mac which will automatically switch voices on the basis of language tags present in word processing or html text? For example, it would switch to a French voice to correctly pronounce a French word or text run in a longer English text.

I'm looking for an extension or service that could speak a web page or email text coded like

<span lang="en-US">In Paris the name of the city is pronounced</span> <span lang="fr-FR">Paris</span>

and switch from an English to a French voice for the last word.

It doesn't appear that Apple's own text-to-speech system will do this, voices normally have to be changed by the user (except possibly when the languages are in different scripts, like English and Hebrew).

I have found one app, Ghostreader +, which does the voice switching, but it requires using the app to add its own language tags and docs are saved in its own non-standard format. Something which works with tags already present for example in html would be a lot more useful.

1 Answer 1

1
+500

You can create your own swift binary and use it in Automator. Here's the example code:

import Foundation
import AVFoundation

class Synth: NSObject, AVSpeechSynthesizerDelegate {
    let synthesizer = AVSpeechSynthesizer()

    init(text: String) {
        super.init()
        let languageCode = NSLinguisticTagger.dominantLanguage(for: text) ?? AVSpeechSynthesisVoiceIdentifierAlex

        let utterance = AVSpeechUtterance(string: text)
        utterance.voice = AVSpeechSynthesisVoice(language: languageCode)
        synthesizer.delegate = self
        synthesizer.speak(utterance)
    }

    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        exit(0)
    }
}

var text = CommandLine.arguments.dropFirst().reduce("", +)
text = text.isEmpty ? "No text" : text
let s = Synth(text: text)

RunLoop.main.run()

Save it in file called main.swift and then in commandline run

swiftc main.swift -o ttslanguage

Place ttslanguage in your home dir, then open Automator.app, select "Service" as a type. Make it run on text input. Place "Run shell script" as a first block and input:

~/ttslanguage "$1"

Now You'll be able to run Text To Speech that detects the language in selected text.

If the text has multuple sentences with different languages you can change the Synth class to (don't forget to import NaturalLanguage):

class Synth: NSObject, AVSpeechSynthesizerDelegate {
    let synthesizer = AVSpeechSynthesizer()
    var lastUtterance: AVSpeechUtterance?

    init(text: String) {
        super.init()
        let tagger = NLTagger(tagSchemes: [.language])
        tagger.string = text
        var utterances = [AVSpeechUtterance]()
        tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .sentence, scheme: .language, options: []) { tag, tokenRange in
                let txt = String(text[tokenRange])
                let languageCode = NSLinguisticTagger.dominantLanguage(for: txt) ?? AVSpeechSynthesisVoiceIdentifierAlex

                let utterance = AVSpeechUtterance(string: txt)
                utterance.voice = AVSpeechSynthesisVoice(language: languageCode)
                utterances.append(utterance)
            return true
        }
        synthesizer.delegate = self

        lastUtterance = utterances.last
        for utterance in utterances {
            synthesizer.speak(utterance)
        }
    }

    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        guard let lastUtterance = lastUtterance else {
            exit(-1)
        }
        if lastUtterance == utterance {
            exit(0)
        }
    }
}


Project with advanced parsing (including HTML + lang attributes) can be found on github page. More info in README.

11
  • @TomGewecke in order to compile the second Synth class you have to add import NaturalLanguage under import AVFoundation NOTE - it's available since macOS 10.14 Jan 12 at 22:33
  • @TomGewecke can you please add sample text to your question? Also can you try to run ~/ttslanguage "hello my friend! Bonjour camarade! Cześć kolego" in Terminal? Jan 12 at 22:41
  • Ah Ok, I get it. My second implementation works only with "sentences" not single words. For your example HTML parsing would be needed instead. Jan 13 at 13:13
  • @TomGewecke If you are selecting the exact HTML you've posted you can use github.com/slozo/TTSLanguage/tree/main , I don't know how to get HTML tags from selected text in Safari :( So I'm using web inspector to select the text and use the Service. Above app parses the HTML, so it reads only the inner text. Please let me know if this is what you need etc. :) Jan 13 at 17:09
  • 1
    Still doesn't work in Catalina, but works fine in Monterey, which is good enough. Thanks for all your effort, I have learned a lot! Jan 15 at 18:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .