How to make "say" command speak AND output to the file at the same time

Solution 1:

It appears difficult or impossible with the built-in say command.

You may be able to redirect the audio from say into a listening AUNetSend that in turn writes to disk and plays the audio; see Audio Hijack below.

espeak and ffplay

You can use the open source espeak and ffplay tools to achieve this. The following command both says "Hello world" and creates a .wav file of the recording:

espeak --stdout "hello world" | tee -a hello.wav | ffplay -i -

The command combines three tools:

  • espeak converts the text into audio.
  • tee is used to divert a copy of stdout to a file.
  • ffplay plays the audio.

espeak can read from a file or web page using the f flag:

espeak --stdout -f myfile.txt | tee -a myfile.wav | ffplay -i -

Another tool such as ffmpeg or iTunes can be used to convert the wav file to an MP3 or AAC file.

Install

To install espeak and ffplay, use HomeBrew or MacPorts. With HomeBrew set up, the install command is:

brew install espeak ffmpeg

Audio Hijack

Alternatively, a good option is to use a third party tool like Audio Hijack:

Record any audio, with Audio Hijack! Save audio from applications like iTunes, Skype or Safari, or from hardware devices like microphones and mixers.

This screen shot shows Audio Hijack taking output from Skype, recording to disk and playing back through the speaker.

Audio Hijack splitting audio

Solution 2:

In one command with the built-in say command, no; in two built-in commands (say and afplay), yes:

$ say -o /tmp/output-file.aiff Hello
$ afplay /tmp/output-file.aiff

In my example, this introduces a very small delay:

$ time sh -c "say Hello" 

real    0m0.778s
user    0m0.008s
sys 0m0.012s

$ time sh -c "say -o /tmp/output-file.aiff Hello && afplay /tmp/output-file.aiff"

real    0m1.048s
user    0m0.050s
sys 0m0.048s