# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. #!/usr/bin/env bash set -euo pipefail set -x # This wrapper is a drop-in replacement for espeak, which does the following: # 1. If the language is not Japanese, it calls espesk from the system directly. # 2. If the language is Japanese, it calls open_jtalk. Models and voice are # provided in the japanese/ folder. They're licensed under BSD-3-Clause, so # we can redistribute them with the game. # 3. If open_jtalk is not available, fall back to espeak. ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REAL_ESPEAK="$(command -v espeak-ng || true)" if [[ -z "${REAL_ESPEAK}" ]]; then REAL_ESPEAK="$(command -v /usr/bin/espeak || command -v espeak)" fi if [[ -z "${REAL_ESPEAK}" ]]; then echo "ERROR: could not find espeak or espeak-ng in PATH" >&2 exit 127 fi OPENJTALK="$(command -v open_jtalk || true)" if [[ -z "${OPENJTALK}" ]]; then echo "WARNING: could not find open_jtalk in PATH" >&2 echo "Falling back to espeak for all languages, including Japanese." >&2 fi PLAYER="$(command -v paplay || command -v aplay || true)" if [[ -z "${PLAYER}" ]]; then echo "WARNING: could not find paplay or aplay in PATH" >&2 echo "Falling back to espeak for all languages, including Japanese." >&2 fi JTALK_DIC="${ROOT}/japanese/dict" JTALK_VOICE="${ROOT}/japanese/voice/nitech_jp_atr503_m001.htsvoice" voice="" declare -a passthrough=() text="" while (($#)); do case "$1" in -v) shift voice="${1:-}" ;; -v*) voice="${1#-v}" ;; # keep other flags for espeak fallback (-a is used by Ren'Py) -*) passthrough+=("$1") # capture arg for flags like -a if [[ "$1" == "-a" && $# -ge 2 ]]; then shift passthrough+=("$1") fi ;; *) # Ren'Py appends the full text as one argument. # If something else adds extra args, last one wins. text="$1" ;; esac shift || true done fallback() { echo "ERROR: OpenJTalk failed; falling back to espeak" >&2 } voice_openjtalk() { [[ -n "${OPENJTALK}" && -x "${OPENJTALK}" ]] || return 2 [[ -d "${JTALK_DIC}" ]] || return 3 [[ -f "${JTALK_VOICE}" ]] || return 4 [[ -n "${text}" ]] || return 5 local tmpwav tmpwav="$(mktemp --suffix=.wav)" set +e printf "%s" "${text}" | "${OPENJTALK}" \ -x "${JTALK_DIC}" \ -m "${JTALK_VOICE}" \ -ow "${tmpwav}" \ >/dev/null 2>&1 local rc=$? set -e # If a generated wav file is empty, fallback. if [[ $rc -ne 0 || ! -s "${tmpwav}" ]]; then rm -f "${tmpwav}" return 10 fi # If there is no player, fallback to espeak. if [[ -z "${PLAYER}" ]]; then rm -f "${tmpwav}" return 11 fi set +e "${PLAYER}" "${tmpwav}" >/dev/null 2>&1 local prc=$? set -e rm -f "${tmpwav}" [[ $prc -eq 0 ]] || return 12 return 0 } if [[ "${voice}" == "ja" ]]; then if voice_openjtalk; then exit 0 else fallback fi fi # Fallback to espeak. if [[ -n "${voice}" ]]; then exec "${REAL_ESPEAK}" -v "${voice}" "${passthrough[@]}" "${text}" else exec "${REAL_ESPEAK}" "${passthrough[@]}" "${text}" fi