音声合成
[tag:]
Raspberry PiやArduinoみたいなシングルボードコンピュータを
使って何かつくろうかと思っているが、たいていのことは
スマートフォンでもできるのであまりいい用途が思いつかない。
とりあえず、合成音声の出力はしたいなーと思い、
いろいろと探してみると、フリーでもよさそうなものが出てくる。
・rospeex
NICTが開発しているらしい。
杉浦孔明さんという方が関わっているようで、
http://rospeex.ucri.jgn-x.jp/nauth_json/jsServices/VoiceTraSS
にPOSTすると合成されたデータが返ってくるようだ(デモ)。
Python + クラウド音声合成 で高品質なアニメ声読み上げを参考に
スクリプトを起こしてみるとうまくいった。
pythonではpyaudioを使うとwavの再生もでき、再生部分も含めるとこんな感じになる。
アニメ声なのが、場面によってはやや使いづらい。
# -*- coding: utf-8 -*-
import sys
import base64
import urllib2
import json
import wave
import time
import pyaudio
tts_url ='http://rospeex.ucri.jgn-x.jp/nauth_json/jsServices/VoiceTraSS'
if __name__=='__main__':
message = sys.argv[1]
# command
tts_command = { 'method':'speak',
'params':['1.1',
{'language':'ja','text':message,'voiceType':"*",'audioType':"audio/x-wav"}]}
obj_command = json.dumps(tts_command)
req = urllib2.Request(tts_url, obj_command)
received = urllib2.urlopen(req).read()
# extract wav file
obj_received = json.loads(received)
tmp = obj_received['result']['audio'] # extract result->audio
speech = base64.decodestring(tmp.encode('utf-8'))
f = open ("tmp.wav",'wb')
f.write(speech)
f.close
w = wave.open("tmp.wav",'rb')
p = pyaudio.PyAudio()
def callback(in_data, frame_count, time_info, status):
data = w.readframes(frame_count)
return (data, pyaudio.paContinue)
stream = p.open(format=p.get_format_from_width(w.getsampwidth()),
channels=w.getnchannels(),
rate=w.getframerate(),
output=True,
stream_callback=callback)
stream.start_stream()
while stream.is_active():
time.sleep(0.1)
stream.stop_stream()
stream.close()
・VoiceTextAPI
こちらはHOYAが開発していて、無料登録するとAPIが使える。
いろいろな言語のライブラリが用意されていたので、
golangのものを使ってみた。github/yosssi/go-voicetext
VoiceTextAPIのバックエンドにもgolangが使われているらしい(スライド)。
ファイルに書かれた内容を句読点等で区切りながら読み上げるサンプル。
splitの実装は句読点を残すために場当たり的実装になっているので要注意。
合成音声の出来はかなりいい感じである。
最近はARM用のパッケージもgolang公式で配布されるようになったので
Raspberry Piなんかでも動かせるのだろう。
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
"time"
"unicode/utf8"
"github.com/yosssi/go-voicetext"
)
const (
APIKEY = "0000000000000" // 無料登録して取得したAPIキー
)
func main() {
c := voicetext.NewClient(APIKEY, nil)
var message string
var cmd *exec.Cmd
var period bool
var scanner *bufio.Scanner
if len(os.Args) >= 2 {
f, err := os.Open(os.Args[1])
if err != nil {
os.Exit(1)
}
scanner = bufio.NewScanner(f)
} else {
scanner = bufio.NewScanner(os.Stdin)
}
isBreak := func(r rune) bool {
switch r {
case '、', '。', ',', '.', '「':
return true
default:
return false
}
}
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
start := 0
for width := 0; start < len(data); start += width {
var r rune
r, width = utf8.DecodeRune(data[start:])
if !isBreak(r) {
break
}
}
for width, i := 0, start; i < len(data); i+= width {
var r rune
r, width = utf8.DecodeRune(data[i:])
if isBreak(r) {
return i + width, data[start:i+3], nil
}
}
if atEOF && len(data) > start {
return len(data), data[start:], nil
}
return start, nil, nil
}
scanner.Split(split)
for scanner.Scan() {
message = scanner.Text()
result, err := c.TTS(message, &voicetext.TTSOptions{
Speaker: voicetext.SpeakerHikari,
Pitch: 80,
Speed: 115,
Volume: 150,
})
if err != nil {
os.Exit(1)
}
if result.ErrMsg != nil {
fmt.Println(result.ErrMsg)
os.Exit(1)
}
if cmd != nil {
cmd.Wait()
os.Remove("tmp.wav")
if period {
time.Sleep(1000*time.Millisecond)
} else {
time.Sleep(400*time.Millisecond)
}
}
if strings.HasSuffix(message, "。") || strings.HasSuffix(message, ".") {
period = true
} else {
period = false
}
f, err := os.Create("tmp.wav")
if err != nil {
os.Exit(1)
}
defer f.Close()
if _, err := f.Write(result.Sound); err != nil {
os.Exit(1)
}
cmd = exec.Command("aplay", "tmp.wav")
err = cmd.Start()
if err != nil {
os.Exit(1)
}
}
if err := scanner.Err(); err != nil {
os.Exit(1)
}
if cmd != nil {
cmd.Wait()
os.Remove("tmp.wav")
}
}