TORICOでは、社内にツール用サーバが何台かあり、社内ツールはすべて Kubernetes + Docker で起動しています。
オフィスでは、決まった時間に Text To Speech (Open Jtalk)で、「換気しましょう」といったアナウンスをするようにしています。
以前は Ubuntu にそのまま Open JTalk をインストールし、生cron で実行していたのですが、最近そのサーバマシンを停止してすべて Kubernetes運用になりましたので、Text To Speech していたタスクを生 cron から Kubernetes Cronjob に移行しました。
Dockerイメージを作る
Dockerfile
まず、Open-JTalk がインストールされた Docker イメージを作ります。
apt install open-jtalk でもインストールできますが、バージョンが古いのでソースからビルドしたほうが使えるオプションが増えていて良いです。音量設定など。
mei ボイスを 1.7, 1.8 の2バージョン入れているのは、切り替えて使えるようにするためです。1.8では1.7からけっこう変化があり、、声のトーンが落ち着いてます。1.7の方がよく通る声なのでそっちを多く使っています。
マルチステージビルドしない場合、1GBほどのイメージサイズになります。この Dockerfile のようにマルチステージビルドした場合、130MBほどで済みました。Alpineは試していません。
FROM ubuntu:18.04 as builder
WORKDIR /var/speech
RUN apt update && \
apt install -y \
unzip \
curl \
build-essential \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*
RUN curl -L -O https://downloads.sourceforge.net/hts-engine/hts_engine_API-1.10.tar.gz
RUN tar zxvf hts_engine_API-1.10.tar.gz
RUN cd hts_engine_API-1.10 && ./configure && make && cd -
RUN curl -L -O https://downloads.sourceforge.net/open-jtalk/open_jtalk-1.11.tar.gz
RUN tar zxvf open_jtalk-1.11.tar.gz
RUN cd open_jtalk-1.11 && ./configure --with-charset=UTF-8 \
--with-hts-engine-header-path=/var/speech/hts_engine_API-1.10/include \
--with-hts-engine-library-path=/var/speech/hts_engine_API-1.10/lib \
&& make && make install
RUN mkdir /usr/share/hts-voice
RUN curl -L -O https://downloads.sourceforge.net/mmdagent/MMDAgent_Example-1.7.zip
RUN unzip MMDAgent_Example-1.7.zip
RUN curl -L -O https://downloads.sourceforge.net/mmdagent/MMDAgent_Example-1.8.zip
RUN unzip MMDAgent_Example-1.8.zip
FROM ubuntu:18.04
WORKDIR /var/speech
RUN apt update && \
apt install -y \
open-jtalk-mecab-naist-jdic \
alsa-utils \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/bin/open_jtalk /usr/local/bin/open_jtalk
COPY --from=builder /var/speech/MMDAgent_Example-1.7/Voice/mei /usr/share/hts-voice/mei17
COPY --from=builder /var/speech/MMDAgent_Example-1.8/Voice/mei /usr/share/hts-voice/mei18
COPY wav ./wav
COPY sh ./sh
TTS再生スクリプト
sh ディレクトリの中身はこのようになっています。
#!/usr/bin/env bash
if [ ! "$1" ]; then
echo "Usage: $0 <speech-text>" >2
exit 1
fi
cd "$(dirname $0)" || exit
./ring-chime.sh
./jsay-female.sh "$1"
#!/bin/sh
TMP=/tmp/jsay.wav
echo "$1" | open_jtalk \
-m /usr/share/hts-voice/mei/mei_normal.htsvoice \
-x /var/lib/mecab/dic/open-jtalk/naist-jdic \
-r 0.7 \
-g 6 \
-ow $TMP && \
./play-wav.sh $TMP
#!/usr/bin/env bash
aplay -D plughw:1 ${1}
#!/usr/bin/env bash
cd "$(dirname $0)" || exit
./play-wav.sh ../wav/chime.wav
./chime-speech.sh こんにちは
というコマンドで、チャイム効果音を鳴らした後、「こんにちは」とTTSで発声するようになっています。
なお、sh/play-wav.sh
の plughw:1
の「1」はサウンドカード番号であり、Dockerホストの環境に依存すると思います。詳細は後ほど書きます。
ビルドスクリプト
docker-compose でも良いのですが、私は下記のような bash スクリプトで Docker イメージのビルドとサーバへのデプロイをしています。
#!/usr/bin/env bash
image_name=torico/torico-speech
container_name=torico-speech
deploy_host=user@deploy-server.example.com
#!/usr/bin/env bash
cd "$(dirname $0)" || exit
. config.sh
docker build . -t ${image_name}
#!/usr/bin/env bash
. config.sh
docker save ${image_name} | bzip2 | ssh ${deploy_host} 'bunzip2 | sudo docker load'
↑手元でビルドした Docker イメージをリモートサーバにコピーします
#!/usr/bin/env bash
./run.sh /bin/bash
#!/usr/bin/env bash
cd "$(dirname $0)" || exit
. config.sh
docker run \
--rm -it \
--device /dev/snd \
--name=${container_name} ${image_name} \
"$@"
スクリプトファイルをたくさん作るのは、エディタ (IntelliJなど) で右クリックからの実行を簡単にするためです。
build.sh でビルドしたら、docker-image-copy-to-remote.sh で SSH でサーバに送りつけます。今回は Docker リポジトリは使っていません。
./run.sh は動作確認のために作っています。Ubuntu用です。Mac では /dev/snd が無いので動きません。
サウンドインターフェイスの確認
--device /dev/snd
をつけて docker コンテナを起動し、サウンドカードの様子を見ます。 --privileged
はなくても動きました
sudo docker run --rm -it --device /dev/snd --name=torico-speech torico/torico-speech /bin/bash
# alsamixer
root@f3a4dc37f277:/var/speech# amixer
Simple mixer control 'IEC958',0
Capabilities: pswitch pswitch-joined
Playback channels: Mono
Mono: Playback [on]
Simple mixer control 'IEC958',1
Capabilities: pswitch pswitch-joined
Playback channels: Mono
Mono: Playback [on]
Simple mixer control 'IEC958',2
Capabilities: pswitch pswitch-joined
Playback channels: Mono
Mono: Playback [on]
Simple mixer control 'IEC958',3
Capabilities: pswitch pswitch-joined
Playback channels: Mono
Mono: Playback [on]
Simple mixer control 'IEC958',4
Capabilities: pswitch pswitch-joined
Playback channels: Mono
Mono: Playback [on]
root@f3a4dc37f277:/var/speech# amixer -c 1
Simple mixer control 'Master',0
Capabilities: pvolume pvolume-joined pswitch pswitch-joined
Playback channels: Mono
Limits: Playback 0 - 87
Mono: Playback 87 [100%] [0.00dB] [on]
Simple mixer control 'Headphone',0
Capabilities: pvolume pswitch
Playback channels: Front Left - Front Right
Limits: Playback 0 - 87
Mono:
Front Left: Playback 87 [100%] [0.00dB] [on]
Front Right: Playback 87 [100%] [0.00dB] [on]
Simple mixer control 'Speaker',0
Capabilities: pswitch
Playback channels: Front Left - Front Right
Mono:
Front Left: Playback [on]
Front Right: Playback [on]
Simple mixer control 'Speaker+LO',0
Capabilities: pvolume
Playback channels: Front Left - Front Right
Limits: Playback 0 - 87
Mono:
Front Left: Playback 87 [100%] [0.00dB]
Front Right: Playback 87 [100%] [0.00dB]
Simple mixer control 'Front Mic',0
Capabilities: pvolume pswitch
Playback channels: Front Left - Front Right
Limits: Playback 0 - 31
Mono:
Front Left: Playback 0 [0%] [-34.50dB] [off]
Front Right: Playback 0 [0%] [-34.50dB] [off]
...
root@f3a4dc37f277:/var/speech# aplay wav/chime.wav
ALSA lib pcm_dmix.c:1052:(snd_pcm_dmix_open) unable to open slave
aplay: main:788: audio open error: No such file or directory
root@f3a4dc37f277:/var/speech# aplay -D plughw:1 wav/chime.wav
Playing WAVE 'wav/chime.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
(再生される)
root@f3a4dc37f277:/var/speech#
サウンドカードは2つ認識されていて、使いたいカード番号は 1 でした。
なので、aplay の引数に -D plughw:1
をつけます。
Kubernetes cronjob
ローカルにある Docker イメージを使って Cronjob を起動します。
ローカルの Docker イメージを使うので、imagePullPolicy は Never です。
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: speech-12-open
namespace: torico
labels:
cronjob: speech-12-open
spec:
concurrencyPolicy: Replace
schedule: "0 12 * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: torico-speech
image: torico/torico-speech
imagePullPolicy: Never
args:
- "/var/speech/sh/chime-speech.sh"
- "12時になりました。…窓を開けて、5分間換気をしてください。"
volumeMounts:
- mountPath: /dev/snd
name: dev-snd
securityContext:
privileged: true
volumes:
- name: dev-snd
hostPath:
path: /dev/snd
docker run の --device
オプションの代わりに、volumeMounts を使います。privileged が必要です。
ちなみに私は、上記ファイルと同じディレクトリに apply.sh とか作って、エディタから右クリックでkubectl を実行するようにしていまします。
#!/usr/bin/env bash
export KUBECONFIG=${HOME}/.kube/remote-server-kubeconfig
kubectl apply -f cronjob.yml