Veskslop command wrapper, optional pitch/speed, measured calibration
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ if [ -z "${DISPLAY:-}" ]; then
|
||||
export DISPLAY=:77
|
||||
fi
|
||||
|
||||
HOST="$ROOT/build/sapi4host"
|
||||
HOST="$ROOT/build/sapi4host-winlib.exe"
|
||||
if [ ! -x "$HOST" ]; then
|
||||
HOST="$ROOT/build/sapi4host.exe"
|
||||
fi
|
||||
|
||||
Executable
+134
@@ -0,0 +1,134 @@
|
||||
#!/bin/sh
|
||||
# Veskslop TTS command-provider wrapper for MicroslopSam.
|
||||
#
|
||||
# Recommended Veskslop configuration:
|
||||
# VESKSLOP_TTS_CMD="<repo>/setup/vekslop-tts.sh {out} {text_file}"
|
||||
# VESKSLOP_TTS_VOICES="$(setup/vekslop-tts.sh --voices)"
|
||||
#
|
||||
# Inputs:
|
||||
# $1 {out} output WAV path
|
||||
# $2 {text_file} UTF-8 text file
|
||||
# $3... optional: voice id, speed multiplier, pitch semitones
|
||||
# env VESKSLOP_TTS_VOICE, VESKSLOP_TTS_SPEED, VESKSLOP_TTS_PITCH
|
||||
#
|
||||
# Output: 48 kHz mono s16le WAV, trimmed, faded, loudness-normalized.
|
||||
set -eu
|
||||
|
||||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
ROOT=$(CDPATH= cd -- "$HERE/.." && pwd)
|
||||
export WINEPREFIX=${WINEPREFIX:-$HOME/.wine-microslopsam}
|
||||
export WINEARCH=win32
|
||||
|
||||
VOICES="sam|Sam|male|en,mary|Mary|female|en,mike|Mike|male|en,keith-bell|Keith Bell|male|en,kerry-watson|Kerry Watson|female|en,mike-telephone|Mike (for Telephone)|male|en,mary-telephone|Mary (for Telephone)|female|en,mike-hall|Mike in Hall|male|en,mike-stadium|Mike in Stadium|male|en,mike-space|Mike in Space|male|en,mary-hall|Mary in Hall|female|en,mary-stadium|Mary in Stadium|female|en,mary-space|Mary in Space|female|en"
|
||||
|
||||
if [ "${1:-}" = "--voices" ]; then
|
||||
echo "$VOICES"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
OUT=${1:?missing out path}
|
||||
TEXTFILE=${2:?missing text file}
|
||||
VOICE=${VESKSLOP_TTS_VOICE:-${3:-sam}}
|
||||
SPEED=${VESKSLOP_TTS_SPEED:-${4:-}}
|
||||
PITCH=${VESKSLOP_TTS_PITCH:-${5:-}}
|
||||
|
||||
case "$VOICE" in
|
||||
sam) ENGVOICE="Sam" ;;
|
||||
mary) ENGVOICE="Mary" ;;
|
||||
mike) ENGVOICE="Mike" ;;
|
||||
keith-bell) ENGVOICE="Keith Bell" ;;
|
||||
kerry-watson) ENGVOICE="Kerry Watson" ;;
|
||||
mike-telephone) ENGVOICE="Mike (for Telephone)" ;;
|
||||
mary-telephone) ENGVOICE="Mary (for Telephone)" ;;
|
||||
mike-hall) ENGVOICE="Mike in Hall" ;;
|
||||
mike-stadium) ENGVOICE="Mike in Stadium" ;;
|
||||
mike-space) ENGVOICE="Mike in Space" ;;
|
||||
mary-hall) ENGVOICE="Mary in Hall" ;;
|
||||
mary-stadium) ENGVOICE="Mary in Stadium" ;;
|
||||
mary-space) ENGVOICE="Mary in Space" ;;
|
||||
*) ENGVOICE="Sam" ;;
|
||||
esac
|
||||
|
||||
# Engine units. Measured naturals: MSTTS ~150, Flextalk ~300 (words/min scale).
|
||||
# Flextalk ignores ITTSAttributes pitch (verified byte-identical output).
|
||||
case "$ENGVOICE" in
|
||||
"Keith Bell"|"Kerry Watson") NAT_SPEED=300; SMAX=390 ;;
|
||||
*) NAT_SPEED=150; SMAX=450 ;;
|
||||
esac
|
||||
SET_ARGS=""
|
||||
if [ -n "$SPEED" ]; then
|
||||
S=$(awk -v m="$SPEED" -v n="$NAT_SPEED" 'BEGIN{printf "%d", n*m+0.5}')
|
||||
[ "$S" -lt 50 ] && S=50
|
||||
[ "$S" -gt "$SMAX" ] && S=$SMAX
|
||||
SET_ARGS="$SET_ARGS -s $S"
|
||||
fi
|
||||
if [ -n "$PITCH" ]; then
|
||||
case "$ENGVOICE" in
|
||||
"Keith Bell"|"Kerry Watson") : ;;
|
||||
*)
|
||||
P=$(awk -v st="$PITCH" 'BEGIN{printf "%d", 100 + st*6.25}')
|
||||
[ "$P" -lt 50 ] && P=50
|
||||
[ "$P" -gt 200 ] && P=200
|
||||
SET_ARGS="$SET_ARGS -p $P" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# The daemon does not export DISPLAY; keep a private Xvfb for the engine.
|
||||
if [ -z "${DISPLAY:-}" ]; then
|
||||
if ! [ -S /tmp/.X11-unix/X77 ]; then
|
||||
Xvfb :77 -screen 0 1024x768x24 >/dev/null 2>&1 &
|
||||
sleep 1
|
||||
fi
|
||||
export DISPLAY=:77
|
||||
fi
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
RAW="$TMP/raw.wav"
|
||||
TXT="$TMP/text.cp1252"
|
||||
|
||||
# SAPI4 is ANSI/CP1252; transliterate UTF-8 (emoji become '?').
|
||||
if command -v iconv >/dev/null 2>&1; then
|
||||
iconv -f UTF-8 -t WINDOWS-1252//TRANSLIT "$TEXTFILE" > "$TXT" 2>/dev/null \
|
||||
|| cp "$TEXTFILE" "$TXT"
|
||||
else
|
||||
cp "$TEXTFILE" "$TXT"
|
||||
fi
|
||||
|
||||
HOST="$ROOT/build/sapi4host-winlib.exe"
|
||||
if [ ! -x "$HOST" ]; then HOST="$ROOT/build/sapi4host.exe"; fi
|
||||
if [ ! -x "$HOST" ]; then echo "engine host not built" >&2; exit 1; fi
|
||||
if ! command -v ffmpeg >/dev/null 2>&1; then echo "ffmpeg not found" >&2; exit 1; fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
if ! "$HOST" -v "$ENGVOICE" -t "$(cat "$TXT")" -o "$RAW" $SET_ARGS \
|
||||
>/dev/null 2>"$TMP/err"; then
|
||||
head -c 400 "$TMP/err" >&2
|
||||
exit 1
|
||||
fi
|
||||
[ -s "$RAW" ] || { echo "engine produced no audio" >&2; exit 1; }
|
||||
|
||||
SIL="silenceremove=start_periods=1:start_duration=0.05:start_silence=0.03:start_threshold=-50dB"
|
||||
PRE="highpass=f=40,$SIL,afade=t=in:st=0:d=0.008,areverse,$SIL,afade=t=in:st=0:d=0.008,areverse"
|
||||
LN="loudnorm=I=-16:TP=-3:LRA=11"
|
||||
|
||||
# Two-pass loudnorm: measure first, then apply with the measured values.
|
||||
M=$(ffmpeg -hide_banner -nostats -i "$RAW" -af "$PRE,$LN:print_format=json" \
|
||||
-f null - 2>&1 || true)
|
||||
I=$(printf '%s' "$M" | sed -n 's/.*"input_i" *: *"\([^"]*\)".*/\1/p')
|
||||
TP=$(printf '%s' "$M" | sed -n 's/.*"input_tp" *: *"\([^"]*\)".*/\1/p')
|
||||
LRA=$(printf '%s' "$M" | sed -n 's/.*"input_lra" *: *"\([^"]*\)".*/\1/p')
|
||||
TH=$(printf '%s' "$M" | sed -n 's/.*"input_thresh" *: *"\([^"]*\)".*/\1/p')
|
||||
OFF=$(printf '%s' "$M" | sed -n 's/.*"target_offset" *: *"\([^"]*\)".*/\1/p')
|
||||
if [ -n "$I" ] && [ -n "$TP" ] && [ -n "$LRA" ] && [ -n "$TH" ] && [ -n "$OFF" ]; then
|
||||
LN="$LN:measured_I=$I:measured_TP=$TP:measured_LRA=$LRA:measured_thresh=$TH:offset=$OFF:linear=true"
|
||||
fi
|
||||
|
||||
TMPOUT="$TMP/out.wav"
|
||||
if ! ffmpeg -hide_banner -nostats -y -i "$RAW" -af "$PRE,$LN" \
|
||||
-ar 48000 -ac 1 -c:a pcm_s16le "$TMPOUT" >/dev/null 2>"$TMP/ff"; then
|
||||
head -c 400 "$TMP/ff" >&2
|
||||
exit 1
|
||||
fi
|
||||
[ -s "$TMPOUT" ] || { echo "post-processing produced no audio" >&2; exit 1; }
|
||||
mv -f "$TMPOUT" "$OUT"
|
||||
Reference in New Issue
Block a user