Last active
March 4, 2016 06:23
-
-
Save NigelThorne/8446065 to your computer and use it in GitHub Desktop.
AutoHotKey_L script to TTS the currently selected text.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'sinatra' | |
require 'win32ole' | |
Sinatra::Application.reset! | |
class MySinatraApp < Sinatra::Base | |
set :port, 7732 #SPEAK | |
@@is_paused = false | |
module SpeechVoiceSpeakFlags | |
#SpVoice Flags | |
SVSFDefault = 0 | |
SVSFlagsAsync = 1 | |
SVSFPurgeBeforeSpeak = 2 | |
SVSFIsFilename = 4 | |
SVSFIsXML = 8 | |
SVSFIsNotXML = 16 | |
SVSFPersistXML = 32 | |
#Normalizer Flags | |
SVSFNLPSpeakPunc = 64 | |
#Masks | |
SVSFNLPMask = 64 | |
SVSFVoiceMask = 127 | |
SVSFUnusedFlags = -128 | |
end | |
@@queue ||= [] | |
get '/' do | |
"""<html><body> | |
<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js\"></script> | |
<script> | |
function post(link) { | |
var xhttp = new XMLHttpRequest(); | |
xhttp.open(\"POST\", link , true); | |
xhttp.send(); | |
} | |
</script> | |
<form id='sayform' action='./say' method='post'>What should I say?: <input name='text' type='text'></input></form></br> | |
<button type='button' onclick=\"post('./pause')\">Pause</button> | |
<button type='button' onclick=\"post('./resume')\">Resume</button> | |
<button type='button' onclick=\"post('./stop')\">Stop</button> | |
<button type='button' onclick=\"post('./toggle')\">Pause/Resume</button> | |
<script> | |
$('#sayform').submit(function(e){ | |
e.preventDefault(); | |
$.ajax({ | |
url:'./say', | |
type:'post', | |
data:$('#sayform').serialize(), | |
success:function(){ | |
$('#sayform input').val(\"\"); | |
} | |
}); | |
}); | |
</script> | |
</body></html>""" | |
end | |
post '/say' do | |
@@is_paused = false | |
voice.Speak(params["text"], SpeechVoiceSpeakFlags::SVSFlagsAsync) | |
return "ok" | |
end | |
post '/say_to_file' do | |
@@is_paused = false | |
out= voice.AudioOutputStream | |
temp_file = "c:\\temp\\output.wav" | |
rm_file(temp_file) | |
fs = WIN32OLE.new('SAPI.SpFileStream') | |
fs.Open(temp_file, 3, true) | |
puts "file open" | |
format_ex = WIN32OLE.new('SAPI.SpWaveFormatEx') | |
voice.AudioOutputStream = fs | |
voice.AudioOutputStream.Format.Type = 39 # SAFT48kHz16BitStereo = 39 | |
puts "stream set" | |
voice.Speak(params["text"], SpeechVoiceSpeakFlags::SVSFPurgeBeforeSpeak) | |
puts "text spoken: #{params["text"]}" | |
fs.Close() | |
voice.AudioOutputStream = out | |
puts "stream reset" | |
return "ok #{temp_file}" | |
end | |
post'/pause' do | |
voice.Pause | |
@@is_paused = true | |
return "ok" | |
end | |
post'/stop' do | |
voice.Speak("", SpeechVoiceSpeakFlags::SVSFPurgeBeforeSpeak) | |
@@is_paused = false | |
return "ok" | |
end | |
post '/resume' do | |
voice.Resume | |
@@is_paused = false | |
return "ok" | |
end | |
post '/toggle' do | |
if (@@is_paused) | |
@@is_paused = false | |
voice.Resume | |
else | |
@@is_paused = true | |
voice.Pause | |
end | |
return "ok" | |
end | |
get '/status' do | |
voice.Speak("status") | |
return "COM Object: #{@@voice.nil?}" | |
end | |
def voice | |
begin | |
@@voice.IsUISupported(nil) if(@@voice != nil) | |
rescue | |
@@voice = nil | |
end | |
@@voice ||= WIN32OLE.new('SAPI.SpVoice') | |
end | |
def rm_file(file) | |
File.delete(file) if File.exist?(file) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# config.ru | |
require 'rubygems' | |
require 'sinatra' | |
require 'rack/reloader' | |
require './app' | |
set :environment, :development | |
use Rack::Reloader, 0 if development? | |
run Sinatra::Application |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source "http://rubygems.org" | |
gem "eventmachine", "1.0.8" | |
gem "thin" | |
gem "win32-service" | |
gem "sinatra" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GEM | |
remote: http://rubygems.org/ | |
specs: | |
daemons (1.2.3) | |
eventmachine (1.0.8) | |
ffi (1.9.10-x86-mingw32) | |
rack (1.6.4) | |
rack-protection (1.5.3) | |
rack | |
sinatra (1.4.6) | |
rack (~> 1.4) | |
rack-protection (~> 1.4) | |
tilt (>= 1.3, < 3) | |
thin (1.6.4) | |
daemons (~> 1.0, >= 1.0.9) | |
eventmachine (~> 1.0, >= 1.0.4) | |
rack (~> 1.0) | |
tilt (2.0.1) | |
win32-service (0.8.7) | |
ffi | |
PLATFORMS | |
x86-mingw32 | |
DEPENDENCIES | |
eventmachine (= 1.0.8) | |
sinatra | |
thin | |
win32-service |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "./say" | |
pause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'win32/service' | |
include Win32 | |
SERVICE_NAME = 'ruby_tts_service' | |
# delete the service | |
# NOTE: if the services applet is up during this operation, the service won't be removed from that ui | |
# unitil you close and reopen it (it gets marked for deletion) | |
#Service.delete(SERVICE_NAME) | |
#path = File.expand_path(File.dirname(__FILE__)).gsub('/','\\') | |
# Create a new service | |
Service.create({ | |
:service_name => SERVICE_NAME, | |
:host => nil, | |
:service_type => Service::WIN32_OWN_PROCESS, | |
:description => 'A tts web service', | |
:start_type => Service::AUTO_START, | |
:error_control => Service::ERROR_NORMAL, | |
:binary_path_name => "\"#{`where ruby`.chomp}\" -C \"#{`echo %cd%`.chomp}\" windows_service.rb", | |
:load_order_group => 'Network', | |
:dependencies => nil, | |
:display_name => SERVICE_NAME | |
}) | |
Service.start(SERVICE_NAME) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encoding: utf-8 | |
#!/usr/bin/env ruby | |
begin | |
SUBSTITUTIONS = { | |
# TODO: Read urls correctly... http://aumel-constash.vsl.com.au:7990/projects/BOND/repos/bddmanagement as http aumel-constash dot vsl dot com dot au, port 7990, projects, BOND, repos, bdd Management | |
/UCASE([0-9]+)/ => "yous case \\1", | |
/\b\.Net\b/ => " dot net ", | |
"/" => "-n-", | |
/\bgit\b/i => " gitt ", | |
/\bLBS\b/ => " Leica Biosystems ", | |
'no.' => "no .", | |
'XP' => 'ex-pea', | |
'APIs' => 'A P eyes', | |
'GOTO' => 'Go Too', | |
' AND ' => ' , and ', | |
' WHEN ' => ' , when ', | |
' THEN ' => ' , then ', | |
/plugin/ => "plug in", | |
# 'WIP' => '"Work In Progress"', | |
'IMO' => '"In My Opinion"', | |
/[A-Z][A-Z][A-Z][A-Z]+((?=[^A-Za-z])|(?!.))/ => lambda{|x|x.downcase}, #All caps becomes word | |
"\u0092" => "'", | |
/n[^a-z0-9\s]t/ => 'n\'t', | |
/[®«]/ => "", # don't read trademark sign | |
/ A / => "'a'", | |
/PMs/ => "pee-emms", | |
/RESTful/ => "restful", | |
/Actionee/i => "Action-e", | |
/Leica/i => "Liker", | |
/Axeda/i => "Exceedar", | |
/\.exe(?=[^a-z])/i => " executable ", | |
/\.txt(?=[^a-z])/i => " text file ", | |
/rebranded/ => "re-branded", | |
/App(?=[\s\.])/ => " application ", | |
'GUI' => " gooee ", | |
/localhost/ => "local host", | |
/tear/ => "tair", | |
/(?<word>[A-Z][a-z]*)(?=[A-Z ,\.;:\t\/])/ => "'\\k<word>' ", # CamelCaseWords should be split by spaces | |
/((?<!.)|(?<=\n))(?<line>[\?\-]\t[^\n]*)\n/m => "\n\\k<line>.\n", # dot points becomes sentences. | |
/\.dll/ => " Deelle el", | |
'\\' => ' slash ', | |
'default' => 'deefault', | |
'×' => 'times', | |
'disconnect' => 'dis-connect', | |
'btw' => 'by the way', | |
'vagaries' => 'vaigeries', | |
'verbalised' => 'verbaleyesed', | |
'(s)' => 's', | |
'BOND-III' => 'BOND3', | |
' is.' => ' is .', | |
'Telerik' => 'Tellerrick', | |
'reading and writing' => ' banana and <phoneme alphabet="x-cmu" ph="T AH0 M EY1 T OW0">writing</phoneme>', | |
} | |
# gitt | |
#Hi, I'm Fred. The time is currently <say-as format="dmy" interpret-as="date">01/02/1960</say-as> | |
require 'cgi' | |
#def to_speakxml(text) | |
# text.gsub!(/\r?\n/, " ") | |
# expression = Regexp.union(SUBSTITUTIONS.keys) | |
# begin | |
# <<-eos | |
# <?xml version="1.0" encoding="UTF-8"?> | |
# <speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-UK"> | |
# <voice xml:lang="en-UK"> | |
# #{SUBSTITUTIONS.reduce(text){ |o, (r,s)| | |
# s.is_a?(Proc) ? o.gsub(r, &s) : o.gsub(r, s) | |
# } | |
# } | |
# </voice> | |
# </speak> | |
# eos | |
# rescue => e | |
# e.to_s | |
# end | |
#end | |
def to_speakxml(text) | |
text.gsub!(/\r?\n/, " ") | |
expression = Regexp.union(SUBSTITUTIONS.keys) | |
begin | |
<<-eos | |
<?xml version="1.0" encoding="UTF-8"?> | |
<vxml version = "2.1"> | |
<form id="F_1"> | |
<block> | |
#{SUBSTITUTIONS.reduce(text){ |o, (r,s)| | |
s.is_a?(Proc) ? o.gsub(r, &s) : o.gsub(r, s) | |
} | |
} | |
</block> | |
</form> | |
</vxml> | |
eos | |
rescue => e | |
e.to_s | |
end | |
end | |
if(__FILE__ == $0) | |
ARGF.set_encoding(Encoding::UTF_8) | |
puts to_speakxml(ARGF.read) | |
end | |
rescue => e | |
puts e.to_s | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encoding: utf-8 | |
require 'win32ole' | |
require "./say" | |
begin | |
text = File.read('c:\\temp\\tmp_ahk_tts_clip.txt') | |
# ARGF.set_encoding(Encoding::UTF_8) | |
# text = ARGF.read(); | |
data = text.bytes.to_a.pack("U*") | |
require_relative 'rephrase' | |
if data == "" | |
toggle | |
else | |
text = to_speakxml(data) | |
File.write('C:\\temp\\tmp_ahk_clip_out.txt',text) | |
say text | |
end | |
rescue => e | |
text = e.message | |
text = "empty message" if text.empty? | |
WIN32OLE.new('SAPI.SpVoice').Speak(text) | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "./say" | |
resume |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require './app.rb' | |
MySinatraApp.run! :host => 'localhost', :port => 7732, :server => 'thin' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'uri' | |
require 'net/http' | |
def say(text) | |
post("say", 'text' => text) | |
end | |
def toggle() | |
post("toggle", 'text' => '') | |
end | |
def pause() | |
post("pause", 'text' => '') | |
end | |
def resume() | |
post("resume", 'text' => '') | |
end | |
def stop() | |
post("stop", 'text' => '') | |
end | |
def post(act, body) | |
uri = URI.parse("http://localhost:7732/#{act}") | |
response = Net::HTTP.post_form(uri, body) | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "./say" | |
stop |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
address: localhost | |
port: 7732 | |
servers: 1 | |
max_conns: 10 | |
max_persistent_conns: 2 | |
timeout: 30 | |
environment: production | |
pid: tmp/pids/thin-production.pid | |
log: log/thin-production.log | |
daemonize: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;;;;;;;;;;;;;;;;;;;TTS;;;;;;;;;;;;;;;;;;;;;; | |
#^D:: ; Win + Ctrl + D | |
ClipSaved := ClipboardAll | |
Clipboard = ; Start off empty to allow ClipWait to detect when the text has arrived. | |
Send ^c | |
ClipWait, 0.1 ; Wait for the clipboard to contain text. | |
FileDelete , c:\temp\tmp_ahk_tts_clip.txt | |
FileAppend , %Clipboard% , c:\temp\tmp_ahk_tts_clip.txt | |
Gui, Add, Button, gPause, &Pause | |
Gui, Add, Button, gResume ys, &Resume | |
Gui, Add, Button, gStop ys, &Stop | |
Gui, Show,, TTS | |
Run, %comspec% /c "".\rephrase_runner.rb" c:\temp\tmp_ahk_tts_clip.txt" ,,;Hide | |
Clipboard := ClipSaved | |
ClipSaved = ; Free the memory | |
Return | |
Stop: | |
Run, %comspec% /c "".\stop.rb"" ,,;Hide | |
Gui, Cancel | |
Return | |
Pause: | |
Run, %comspec% /c "".\pause.rb"" ,,;Hide | |
Return | |
Resume: | |
Run, %comspec% /c "".\resume.rb"" ,,;Hide | |
Return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This runs a simple sinatra app as a service | |
LOG_FILE = 'C:\\sinatra_daemon.log' | |
require 'rubygems' | |
require 'sinatra/base' | |
require './app' | |
#begin | |
require 'win32/daemon' | |
include Win32 | |
$stdout.reopen("thin-server.log", "w") | |
$stdout.sync = true | |
$stderr.reopen($stdout) | |
class TestDaemon < Daemon | |
def service_main | |
log 'started' | |
MySinatraApp.run! :host => 'localhost', :port => 7732, :server => 'thin' | |
while running? | |
log 'running' | |
sleep 10 | |
end | |
end | |
def service_stop | |
log 'ended' | |
exit! | |
end | |
def log(text) | |
File.open('log.txt', 'a') { |f| f.puts "#{Time.now}: #{text}" } | |
end | |
end | |
TestDaemon.mainloop | |
#rescue Exception => err | |
#File.open(LOG_FILE,'a+'){ |f| f.puts " ***Daemon failure #{Time.now} err=#{err} " } | |
#raise | |
#end |
You could have fun with this... http://xkcd.com/1288/
It how frees up the clipboard before it's stop reading. this makes it much nicer to use.
TODO: make it stop reading when you trigger it with no text, or start reading something else with new text.
Added index web page so you can Pause/Say/Stop/Resume from there if you like.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This script replaced the clipboard .. but currently has a side effect that while reading you can't cut/paste any more! :(
I'm now piping the text through a rephrase script that rewrites things to read them how I like them.
TODO: I may move the call to COM into the ruby.. then I can start it on another thread and free up the clipboard earlier.
I may also introduce a sinatra app or a service, running locally, that you send text to.. this would let me start/stop/pause/replace what's being read. Currently you have to wait for it to finish reading.