134 lines
3.2 KiB
Plaintext
Executable File
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
|