0

I combined these examples from the official pyttsx3 documentation:

I want to save an audio file containing all the voices.

Code

import pyttsx3

engine = pyttsx3.init()
voices = engine.getProperty('voices')
for voice in voices:
    engine.setProperty('voice', voice.id)
    engine.say('The quick brown fox jumped over the lazy dog.')

engine.runAndWait()

# this uses the last voice only
engine.save_to_file('The quick brown fox jumped over the lazy dog.' , 'audio.mp3')

Issue

The engine.save_to_file('The quick brown fox jumped over the lazy dog.' , 'audio.mp3') will use the last voice reading the sentence.

Question

Is it possible to do so? Are there other Text-To-Speach engines (tts) that can do?

hc_dev
  • 8,389
  • 1
  • 26
  • 38
Juan Mir
  • 1
  • 1
  • Welcome to SO, Juan ️ Great you tried to improve the code-formatting. Use three backticks "```" for [fenced code blocks](https://meta.stackoverflow.com/questions/251361/how-do-i-format-my-code-blocks) and link to examples you used - like I did edit your answer. – hc_dev Dec 30 '22 at 10:49
  • @hc_dev okay! I'll do that. Thanks! – Juan Mir Dec 30 '22 at 10:51
  • Is this indentation of the last 2 lines wanted? Are you new to Python? See the [**important role of indentation** for blocks like loops](https://pythonguides.com/block-indentation-in-python/) in Python. – hc_dev Dec 30 '22 at 10:56

2 Answers2

0

You can include the last part of your code ( engine.save_to_file('The quick brown fox jumped over the lazy dog.' , 'audio.mp3') ) inside the while loop to obtain seperate audio files with each of the voice.

import pyttsx3

engine = pyttsx3.init()
voices = engine.getProperty('voices')

for i, voice in enumerate(voices, start=1):
    engine.setProperty('voice', voice.id)
    engine.say('The quick brown fox jumped over the lazy dog.')
    engine.save_to_file('The quick brown fox jumped over the lazy dog.' , f'audio_{i}.mp3')
    

engine.runAndWait()

This will save each voice to files in the order audio1.py, audio2.py, etc....

If you want to concatenate these audio files together then there are modules for such purposes. Refer to https://www.thepythoncode.com/article/concatenate-audio-files-in-python regarding this.

mani-hash
  • 346
  • 1
  • 10
  • 1
    In case you like to improve your answer, simplify numbered filenames with: `for i, voice in enumerate(voices):` (no need for separate counter) and `f"audio_{i}.mp3"` (f-string to add underscore suffix). – hc_dev Dec 30 '22 at 11:45
0

Yes, it is possible to save the spoken sentence in using all available voices.

You maybe have not used the pyttsx3 API correctly. Or there is an logical bug with indentation after the loop.

First, let's inspect what your code does.

Use print to debug

import pyttsx3

engine = pyttsx3.init()
voices = engine.getProperty('voices')
print(f"found {len(voices)} voices to say the sentence")   # use f-string to print count of voices
for voice in voices:
    engine.setProperty('voice', voice.id)
    print('changed voice to: ', voice)
    engine.say('The quick brown fox jumped over the lazy dog.')

engine.runAndWait()

# this uses the last voice only
print('using engine current voice to save:', engine.getProperty('voice'))
engine.save_to_file('The quick brown fox jumped over the lazy dog.' , 'audio.mp3')

# Did it save?

Prints all the voices (here showing only first and last):

found 69 voices to say the sentence
changed voice to:  <Voice id=afrikaans
          name=afrikaans
          languages=[b'\x05af']
          gender=male
          age=None>
...
changed voice to:  <Voice id=cantonese
          name=cantonese
          languages=[b'\x05zh-yue']
          gender=male
          age=None>
Full dictionary is not installed for 'ru'
Full dictionary is not installed for 'zhy'
using engine current voice to save: cantonese

For me the file audio.mp3 was not created or saved.

Issues

(1) The file was not saved

Maybe because we need a final engine.runAndWait() command to start the engine, generate the audio and save the file? See docs example "Saving voice to a file" and runAndWait():

runAndWait() → None

Blocks while processing all currently queued commands. Invokes callbacks for engine notifications appropriately. Returns when all commands queued before this call are emptied from the queue.

Here we have the reason. It was not saved because the engine did run before queuing the save command:

engine.runAndWait()

# this uses the last voice only
engine.save_to_file('The quick brown fox jumped over the lazy dog.' , 'audio.mp3')

(2) The current (last set) voice is used to save

The current voice set in the engine is also the last set in your loop. To save an audio-file for each voice, you should include the save command inside the loop.

Move the line up and indent to be aligned within the loop's body. Since you are currently using the fix filename audio.mp3, it would be wise to change it for each iteration, e.g. prefix the filename with the current voice.id like filename = "voice.id + "audio.mp3".

Fixed solution

Save each voice separately

This should save many files, each with another voice.

import pyttsx3

engine = pyttsx3.init()  # engine begins to queue following commands 

voices = engine.getProperty('voices')
print(f"found {len(voices)} voices to say the sentence")   # use f-string to print count of voices
for voice in voices:
    engine.setProperty('voice', voice.id)
    print('using engine current voice to save:', engine.getProperty('voice').name)
    filename_by_voice = f"audio_{voice.id}.mp3"
    print('saving voice to: ', filename_by_voice)
    engine.save_to_file('The quick brown fox jumped over the lazy dog.' , filename_by_voice)

# final command to start the engine and execute queued commands
engine.runAndWait()  #  save the file(s)

Note: I removed the say command to speed up things.

hc_dev
  • 8,389
  • 1
  • 26
  • 38