Last active
December 2, 2015 02:51
-
-
Save raccy/ab6ed1cf0b2d5560bd8f to your computer and use it in GitHub Desktop.
Viqo経由でめいちゃんにコメをしゃべって貰おうとしたら、なぜかこうなった。
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
#!/usr/bin/env ruby | |
# coding: utf-8 | |
=begin | |
kometalk.rb | |
Copyright (c) 2015 IGARASHI Makoto (raccy) | |
This software is released under the MIT License. | |
http://opensource.org/licenses/mit-license.php | |
コマンドラインからの入力文字をOpen JTalkでしゃべってくれます。 | |
実行はすぐに終わって、裏でサーバが立ち上がって、しゃべります。 | |
サーバはしばらくすると、落ちます。 | |
Mac以外で動くかどうかは知らん。Windowsは無理だと思う。 | |
使い方: | |
ruby kometalk.rb ほげほげ | |
でよみます。 | |
下の__END__以下でopne_jtalkのパスとかは指定して下さい。 | |
open_jtalkの細かいオプションは・・・直接ソースを触って下さい。 | |
=end | |
require "pathname" | |
require "timeout" | |
require "yaml" | |
require "logger" | |
class Kometalk | |
DEFAULT_CONFIG = { | |
open_jtalk: "open_jtalk/bin/open_jtalk", | |
dic_dir: "open_jtalk/dic", | |
htvoice: "open_jtalk/voice/mei_normal.htsvoice", | |
text_file: "kometalk.txt", | |
wave_file: "kometalk.wav", | |
info_file: "kometalk.inf", | |
lock_file: "kometalk.lock", | |
fifo_file: "kometalk.fifo", | |
pid_file: "kometalk.pid", | |
log_file: "kometalk.log", | |
server_timeout: 60, | |
client_timeout: 10, | |
play_wave: "/usr/bin/afplay", | |
learn_yaml: "learn.yml", | |
learn_say: "%sって読む。めぃ、覚えた。", | |
learn_non: "%sって読まない。めぃ、忘れた。", | |
learn_unk: "%sのこと、めぃ、知らない。", | |
learn_reg: "その正規表現は、%sって読むのね。。", | |
learn_nor: "正規表現が間違っているよ。", | |
} | |
def initialize(work_dir, config) | |
@work_path = Pathname(work_dir) | |
@config = DEFAULT_CONFIG.merge(config) | |
@learn = [] | |
@logger = Logger.new(path(:log_file)) | |
@logger.level = Logger::INFO | |
end | |
def path(sym) | |
return @work_path + @config[sym] | |
end | |
def say(*str_list) | |
path(:lock_file).open("w") do |lock_file| | |
lock_file.flock(File::LOCK_EX) | |
run_server(lock_file) | |
begin | |
timeout(@config[:client_timeout]) do |i| | |
path(:fifo_file).open("a") do |fifo_file| | |
str_list.each do |str| | |
fifo_file.puts str | |
end | |
end | |
end | |
rescue Timeout::Error | |
@logger.error("client timeout") | |
end | |
end | |
end | |
def run_server(lock_file) | |
if path(:pid_file).file? | |
path(:pid_file).open("r+") do |pid_file| | |
unless pid_file.flock(File::LOCK_EX | File::LOCK_NB) | |
@logger.debug("server running") | |
return | |
end | |
end | |
end | |
unless path(:fifo_file).exist? | |
system("mkfifo #{path(:fifo_file)}") | |
end | |
@logger.info("fork child") | |
fork do | |
@logger.debug("close lock file") | |
lock_file.close | |
@logger.debug("setsid") | |
Process.setsid | |
@logger.debug("setproctitle") | |
begin | |
Process.setproctitle("(#{File.basename(__FILE__)})") | |
rescue => e | |
@logger.debug(e.message) | |
$0 = "(#{File.basename(__FILE__)})" | |
end | |
@logger.info("fork server") | |
fork do | |
begin | |
@logger.debug("daemonize") | |
Process.daemon | |
path(:pid_file).open("w") do |pid_file| | |
pid_file.flock(File::LOCK_EX) | |
pid_file.puts(Process.pid) | |
pid_file.flush | |
if path(:learn_yaml).file? | |
@logger.info("load learn yaml") | |
@learn = YAML.load(path(:learn_yaml).read) | |
unless @learn.is_a?(Array) | |
@logger.info("initilaize learn") | |
@learn = [] | |
end | |
end | |
path(:fifo_file).open("r") do |fifo_file| | |
begin | |
while true | |
line = nil | |
timeout(@config[:server_timeout]) do |i| | |
until line = fifo_file.gets | |
@logger.debug("sleep 1") | |
sleep 1 | |
end | |
end | |
talk(line) | |
end | |
rescue Timeout::Error | |
@logger.info("server timeout") | |
end | |
end | |
@logger.info("save learn yaml") | |
path(:learn_yaml).write(YAML.dump(@learn)) | |
end | |
rescue => e | |
@logger.error(e.message) | |
end | |
exit!(0) | |
end | |
exit!(0) | |
end | |
Process.wait | |
end | |
def talk(str) | |
@logger.debug("talk #{str}") | |
str.encode!('UTF-8', 'UTF-8-MAC') | |
case str | |
when /^s\/((?:[^\/]|\\\/)+)\/((?:[^\/]|\\\/)+)\/g$/ | |
reg_str = $1 | |
replace = $2 | |
begin | |
regexp = Regexp.compile(reg_str) | |
@logger.info("learn s/#{reg_str}/#{replace}/g") | |
@learn[0] ||= {} | |
@learn[0][regexp] = replace | |
str = @config[:learn_reg] % replace | |
rescue RegexpError | |
@logger.warn("invalid regexp #{reg_str}") | |
str = @config[:learn_nor] | |
end | |
when /^\s*教育\s*[((]\s*(\S+)\s*[==]\s*(\S+)\s*[)|)]\s*$/ | |
word = $1 | |
yomi = $2 | |
@logger.info("learn #{word} = #{yomi}") | |
@learn[word.size] ||= {} | |
@learn[word.size][word] = yomi | |
str = @config[:learn_say] % $2 | |
when /^\s*忘却\s*[((]\s*(\S+)\s*[))]\s*$/ | |
word = $1 | |
@learn[word.size] ||= {} | |
yomi = @learn[word.size].delete(word) | |
if yomi | |
@logger.info("forget #{word}") | |
str = @config[:learn_non] % yomi | |
else | |
@logger.warn("unknown #{word}") | |
str = @config[:learn_unk] % word | |
end | |
else | |
@learn.reverse_each do |learn_child| | |
if learn_child.is_a?(Hash) | |
learn_child.each_pair do |key, value| | |
str.gsub!(key, value) | |
end | |
end | |
end | |
end | |
@logger.info("jtalk #{str}") | |
path(:text_file).open("w") do |io| | |
io.puts str | |
end | |
cmd = "" | |
cmd << path(:open_jtalk).to_s | |
cmd << " -x " << path(:dic_dir).to_s | |
cmd << " -m " << path(:htvoice).to_s | |
cmd << " -ow " << path(:wave_file).to_s | |
cmd << " -ot " << path(:info_file).to_s | |
# customize | |
# cmd << " -s i " # sampling frequency [ auto][ 1-- ] | |
# cmd << " -p i " # frame period (point) [ auto][ 1-- ] | |
# cmd << " -a f " # all-pass constant [ auto][ 0.0-- 1.0] | |
# cmd << " -b 0.0 " # postfiltering coefficient [ 0.0][ 0.0-- 1.0] | |
cmd << " -r 1.2 " # speech speed rate [ 1.0][ 0.0-- ] | |
# cmd << " -fm 0.0 " # additional half-tone [ 0.0][ -- ] | |
# cmd << " -u 0.5 " # voiced/unvoiced threshold [ 0.5][ 0.0-- 1.0] | |
# cmd << " -jm 1.0 " # weight of GV for spectrum [ 1.0][ 0.0-- ] | |
# cmd << " -jf 1.0 " # weight of GV for log F0 [ 1.0][ 0.0-- ] | |
cmd << " " << path(:text_file).to_s | |
@logger.debug("exec #{cmd}") | |
system(cmd) | |
system("#{path(:play_wave).to_s} #{path(:wave_file).to_s}") | |
end | |
end | |
if __FILE__ == $0 | |
config = YAML.load(DATA).each_with_object({}){|(k,v),m|m[k.to_s.intern]=v} | |
kometalk = Kometalk.new(File.expand_path(File.dirname(__FILE__)), config) | |
kometalk.say(*ARGV) | |
end | |
__END__ | |
open_jtalk: open_jtalk/bin/open_jtalk | |
dic_dir: open_jtalk/dic | |
htvoice: open_jtalk/voice/mei_normal.htsvoice | |
play_wave: /usr/bin/afplay |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment