Files
espeak-wrapper/espeak
2026-02-23 00:31:49 +03:00

134 lines
3.2 KiB
Plaintext
Executable File

# 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 <amp>
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