Skip to content

Commit

Permalink
Release 2020.04.07. Added settings support which can be used to confi…
Browse files Browse the repository at this point in the history
…gure TTS rate and pre-processing. The TTS engine now has to be initialized before use (done automatically, lazily), and can be re-initialized by the user after making settings changes.
  • Loading branch information
scholer committed Apr 7, 2020
1 parent c71df64 commit 290a8a4
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 25 deletions.
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ macOS and Linux is not currently supported, but the idea is to support both:
Alternatively, we could call `say` from a subprocess
(this can also easily be implemented manually using e.g. the build system.

On Linux, the "espeak" speech library (`libespeak.so`) is used for TSS.
On Linux, the "espeak" speech library (`libespeak.so`) could be used for TSS.



Expand Down Expand Up @@ -52,7 +52,25 @@ To speak text from Sublime Text:
`TTS: Reinitialize` to reset the TTS engine.


## Configuring Sublime Text keyboard shortcuts:
## Settings:

To adjust your TextToSpeech settings, go to "Preferences" -> "Package Settings"
-> TextToSpeech -> TextToSpeech Settings. You should then see a split window with two
tabs. The tab on the left is the default settings; the tab on the right is your
user-specified settings. Copy the settings you want to change from the default settings
on the left to the user-settings on the right. Do not modify the default settings,
they will be overwritten whenever the plugin is updated!

The currently-available settings are:

* `"debug_print"` (default: false) - set this to `true` to have the plugin write a bunch of debugging output to the console.
* `"replace_trivial_eol_newline"` (default: true) -
* `"tts_rate"`: (default: 0) - increase this to increase the rate/speed of the TTS synthesis.
* `"tts_volume"`: (default: 100) - decrease this to decrease the TTS volume.
* `"tts_voice"`: (default: null, meaning use default voice) - the voice to use, e.g. "Microsoft David", or "Microsoft Zira". This depends on which voices you have available on your system!


## Configuring Sublime Text TextToSpeech keyboard shortcuts:

It is quite easy to set up Sublime Text so you can control TextToSpeech
using your keyboard, you just have to configure your "Key Bindings".
Expand Down Expand Up @@ -85,9 +103,10 @@ file in the left panel should now look like this:
]
```

The keymaps defined above requires you to press `ctrl+t`, followed by another keypress
The keymaps defined above requires you to first press `ctrl+t`,
and then press `ctrl` plus one of `t`, `p`, `r`, `s`, or `a`.

You can now control TTS playback by pressing **`ctrl+t`,
This will allow you to control TTS playback by pressing **`ctrl+t`**,
followed by one of the following key presses
to start/pause/resume/skip/stop TTS playback:

Expand All @@ -97,5 +116,5 @@ to start/pause/resume/skip/stop TTS playback:
* **`ctrl+s`** to skip to the next sentence,
* **`ctrl+a`** to skip all remaining text and stop text-to-speech synthesis.

You can now start tts by pressing **`ctrl+t` *twice*** rapidly on your keyboard.
So, in order to start tts, you would just press **`ctrl+t` *twice*** rapidly on your keyboard.

6 changes: 5 additions & 1 deletion TextToSpeech.sublime-settings
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// TextToSpeech default settings:
// Copy items from here to your user-customized "Settings - User" file.
{

"debug_print": false,
"replace_trivial_eol_newline": true,
"tts_rate": 1,
"tts_volume": 100,
"tts_voice": null, // None means use default voice.
}
6 changes: 6 additions & 0 deletions messages/2020.04.07.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

### Release 2020.04.07:

* Added support for user-configurable settings, which can be used to configure TTS rate and pre-processing.
* The TTS engine is now initialized on first use, increasing package loading performance.
The TTS engine can be re-initialized manually after changing the settings to effectuate the changes.
3 changes: 1 addition & 2 deletions messages/install.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ To speak text from within Sublime Text using TextToSpeech:
6. If something goes wrong and the TTS engine starts misbehaving, you can use
`TTS: Reinitialize` to reset the TTS engine.


If you would like to review the TextToSpeech usage instructions at a later time,
just select `Preferences -> Package Settings -> TextToSpeech -> README` from the menu.

The instructions also provide details on how to configure your Sublime Text "key bindings"
The instructions also provide details on how to configure your Sublime Text key bindings
so you can start/pause/resume/skip/stop TTS using keyboard shortcuts.
27 changes: 25 additions & 2 deletions simple_tts/tts_win.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,60 @@
import win32com.client

# speech = win32com.client.Dispatch('System.Speech.Synthesis.SpeechSynthesizer') # Doesn't work
spvoice = win32com.client.Dispatch('SAPI.SPVoice')
spvoice = None # Must initialize voice first.


def reinitialize_voice():
def reinitialize_voice(rate=None, volume=None, voice=None):
""" Re-initialize voice engine and reset rate, volume, and voice.
Note that *pitch* is not a persistent setting, but rather an xml command
that needs to be embedded in the byte-encoded input.
"""
global spvoice
# del spvoice
spvoice = win32com.client.Dispatch('SAPI.SPVoice')
if rate is not None:
spvoice.rate = rate
if volume is not None:
spvoice.volume = volume
if voice is not None:
# Volume should be e.g. "Microsoft Sam", or "Microsoft David"
spvoice.voice = voice
return spvoice


def speak(sentence):
if spvoice is None:
reinitialize_voice()
return spvoice.Speak(sentence.encode('utf-8'), 19) # returns immediately.


def pause():
if spvoice is None:
reinitialize_voice()
return spvoice.Pause() # returns immediately.


def resume():
if spvoice is None:
reinitialize_voice()
return spvoice.Resume() # returns immediately.


def skip(num_skip=1):
if spvoice is None:
reinitialize_voice()
return spvoice.Skip("Sentence", num_skip) # returns immediately.


def skip_all():
if spvoice is None:
reinitialize_voice()
return skip(num_skip=1000)


def speak_and_wait_until_done(sentence):
if spvoice is None:
reinitialize_voice()
return spvoice.WaitUntilDone(sentence.encode('utf-8'), 19) # blocks until done.


Expand Down
81 changes: 66 additions & 15 deletions tts_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,34 @@
import sublime
import sublime_plugin
import sys
import re

if sys.platform == "win32":
from .simple_tts import tts_win as tts
else:
raise RuntimeError("TextToSpeech currently only supports Windows..")
raise RuntimeError("TextToSpeech currently only supports Windows!")


SETTINGS_NAME = 'TextToSpeech.sublime-settings'
DEBUG_PRINT = False


def reinitialize_with_settings():
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
if debug_print:
print("TTS settings:")
print("rate:", settings.get("tts_rate"))
print("volume:", settings.get("tts_volume"))
print("voice:", settings.get("tts_voice"))
return tts.reinitialize_voice(
rate=settings.get("tts_rate"),
volume=settings.get("tts_volume"),
voice=settings.get("tts_voice"),
)
reinitialize_with_settings()


class TtsSpeakCommand(sublime_plugin.WindowCommand):
""" Command to speak selected text using TTS (speech synthesis).
This should be a WindowCommand, since we don't need to make any edits.
Expand All @@ -27,18 +46,40 @@ class TtsSpeakCommand(sublime_plugin.WindowCommand):
sublime.run_command("tts_speak")
"""
def run(self):
# view = self.view
# Get settings:
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
replace_trivial_eol_newline = settings.get("replace_trivial_eol_newline")
regex_substitutions = settings.get("regex_substitutions") # A list with <regex-pattern>, <substitute-text>
if debug_print:
print("\nTTS Speak command invoked, settings are:")
print(" debug_print: ", debug_print)
print(" replace_trivial_eol_newline: ", replace_trivial_eol_newline)
print(" regex_substitutions: ", "".join("\n - %s" % (tup,) for tup in regex_substitutions) if regex_substitutions else regex_substitutions)

# view = self.view # for TextCommands, not WindowCommands
view = self.window.active_view()
text = "\n\n".join(view.substr(region) for region in view.sel())
if not text:
text = view.substr(sublime.Region(0, view.size()))
if not text:
print("No text selected; no text in buffer.")
return
if DEBUG_PRINT:
if replace_trivial_eol_newline:
trivial_eol_newline_regex = re.compile(r"\n(?=\w)") # Newline not followed by another newline.
text, counts = trivial_eol_newline_regex.subn(" ", text) # Replace trivial newlines in text with a space.
if debug_print:
print("Substituted %s trivial end-of-line newlines a space." % (counts, ))
if regex_substitutions:
for pattern, repl in regex_substitutions:
text, counts = re.subn(pattern, repl, text)
if debug_print:
print("Substituted %s instances of pattern %r with string %r:" % (counts, pattern, repl))

if debug_print:
print("tts.speak(<%s chars>) ..." % (len(text),), end="")
ret = tts.speak(text)
if DEBUG_PRINT:
if debug_print:
print(ret)


Expand All @@ -51,10 +92,12 @@ class TtsPauseCommand(sublime_plugin.WindowCommand):
sublime.run_command("tts_pause")
"""
def run(self):
if DEBUG_PRINT:
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
if debug_print:
print("tts.pause() ... ", end="")
ret = tts.pause()
if DEBUG_PRINT:
if debug_print:
print(ret)


Expand All @@ -67,10 +110,12 @@ class TtsResumeCommand(sublime_plugin.WindowCommand):
sublime.run_command("tts_resume")
"""
def run(self):
if DEBUG_PRINT:
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
if debug_print:
print("tts.resume() ... ", end="")
ret = tts.resume()
if DEBUG_PRINT:
if debug_print:
print(ret)


Expand All @@ -82,10 +127,12 @@ class TtsSkipCommand(sublime_plugin.WindowCommand):
sublime.run_command("tts_skip")
"""
def run(self, num_skip=1):
if DEBUG_PRINT:
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
if debug_print:
print("tts.skip(%s) ... " % (num_skip,), end="")
ret = tts.skip(num_skip=num_skip)
if DEBUG_PRINT:
if debug_print:
print(ret)


Expand All @@ -97,10 +144,12 @@ class TtsSkipAllCommand(sublime_plugin.WindowCommand):
sublime.run_command("tts_skip_all")
"""
def run(self):
if DEBUG_PRINT:
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
if debug_print:
print("tts.skip_all() ... ", end="")
ret = tts.skip_all()
if DEBUG_PRINT:
if debug_print:
print(ret)


Expand All @@ -112,8 +161,10 @@ class TtsReinitializeCommand(sublime_plugin.WindowCommand):
sublime.run_command("tts_reinitialize")
"""
def run(self):
if DEBUG_PRINT:
settings = sublime.load_settings(SETTINGS_NAME)
debug_print = settings.get("debug_print", DEBUG_PRINT)
if debug_print:
print("tts.reinitialize_voice() ... ", end="")
ret = tts.reinitialize_voice()
if DEBUG_PRINT:
ret = reinitialize_with_settings()
if debug_print:
print(ret)

0 comments on commit 290a8a4

Please sign in to comment.