Skip to content

Instantly share code, notes, and snippets.

@alexkuz
Last active August 29, 2024 22:44
Show Gist options
  • Save alexkuz/f24f93245ff80458c9b6ec93c644c40b to your computer and use it in GitHub Desktop.
Save alexkuz/f24f93245ff80458c9b6ec93c644c40b to your computer and use it in GitHub Desktop.
Install Piper TTS as speech dispatcher module
#!/bin/sh
set -e
BASE_PIPER_VOICES_URL=${BASE_PIPER_VOICES_URL:-https://huggingface.co/rhasspy/piper-voices/resolve/main}
INSTALL_DIR=${INSTALL_DIR:-~/.local/share/speech-dispatcher-piper}
CONFIG_DIR=${CONFIG_DIR:-~/.config/speech-dispatcher/modules}
CONFIG_PATH=$CONFIG_DIR/piper-generic.conf
PLATFORM=${PLATFORM:-$(uname -m)}
PIPER_BIN_GZ_URL=${PIPER_BIN_GZ_URL:-https://github.com/rhasspy/piper/releases/latest/download/piper_linux_$PLATFORM.tar.gz}
PIPER_GENERIC_CONF=$(cat <<EOF
DefaultVoice "en_GB-alan-low"
GenericCmdDependency "sox"
GenericCmdDependency "jq"
GenericExecuteSynth \
"cd $INSTALL_DIR && \
./check_piper_voice.sh \$VOICE && \
printf %s \'\$DATA\' \
| ./piper/piper --model \'voices/\$VOICE.onnx\' --output_raw \
| sox -v \$VOLUME -r \$(jq .audio.sample_rate < \'voices/\$VOICE.onnx.json\') -c 1 \
-b 16 -e signed-integer -t raw - -t wav - tempo \$RATE pitch \$PITCH norm \
| \$PLAY_COMMAND"
GenericRateAdd 1
GenericPitchAdd 1
GenericVolumeAdd 1
GenericRateMultiply 1
GenericPitchMultiply 750
GenericVolumeMultiply 1
EOF
)
# checks if piper voice is present and download it if not
CHECK_PIPER_VOICE_SH=$(cat <<EOF
#!/bin/sh
VOICE=\$1
VOICE_MODEL=\$VOICE.onnx
VOICE_JSON=\$VOICE.onnx.json
BASE_PIPER_VOICES_URL=$BASE_PIPER_VOICES_URL
if ! [ -f "voices/\$VOICE_MODEL" ] || ! [ -f "voices/\$VOICE_JSON" ]; then
LANG_FULL=\$(echo "\$VOICE" | cut -d '-' -f 1)
LANG_SHORT=\$(echo "\$LANG_FULL" | cut -d '_' -f 1)
NAME=\$(echo "\$VOICE" | cut -d '-' -f 2)
QUALITY=\$(echo "\$VOICE" | cut -d '-' -f 3)
if ! [ -f "voices/\$VOICE_JSON" ]; then
curl -s -L -C - -o "voices/\$VOICE_JSON.download" "\$BASE_PIPER_VOICES_URL/\$LANG_SHORT/\$LANG_FULL/\$NAME/\$QUALITY/\$VOICE_JSON?download=true"
mv "voices/\$VOICE_JSON.download" "voices/\$VOICE_JSON"
fi
if ! [ -f "voices/\$VOICE_MODEL" ]; then
curl -s -L -C - -o "voices/\$VOICE_MODEL.download" "\$BASE_PIPER_VOICES_URL/\$LANG_SHORT/\$LANG_FULL/\$NAME/\$QUALITY/\$VOICE_MODEL?download=true"
mv "voices/\$VOICE_MODEL.download" "voices/\$VOICE_MODEL"
fi
fi
EOF
)
echo "\033[1;36mInstalling piper dependencies: \033[0mjq, sox"
apt -qq install jq sox
mkdir -p "$INSTALL_DIR/voices"
if ! [ -d "$CONFIG_DIR" ]; then
echo "\033[1;36mConfig directory not found, \033[0mexecuting spd-conf"
spd-conf -n
fi
# if config exists, backup old version and create new one
if [ -f "$CONFIG_PATH" ]; then
echo "\033[1;36mConfig file found, \033[0mbacking up old version"
i=1
while [ -f "$CONFIG_PATH.$i.bak" ]; do
i=$((i+1))
done
mv "$CONFIG_PATH" "$CONFIG_PATH.$i.bak"
fi
touch "$CONFIG_PATH"
echo "\033[1;36mDownload voice list and create piper config\033[0m"
VOICE_JSON=$(mktemp)
curl -s -L -o "$VOICE_JSON" "$BASE_PIPER_VOICES_URL/voices.json"
VOICES=$(jq -r 'map(.key) | @sh' < "$VOICE_JSON")
rm "$VOICE_JSON"
for VOICE in $VOICES; do
VOICE=${VOICE#\'} && VOICE=${VOICE%\'}
LANG_FULL=$(echo "$VOICE" | cut -d '-' -f 1)
# if voice contains "female", use FEMALE1 as voice name, otherwise use MALE1
VOICE_GENDER=MALE1
case "$VOICE" in
*female*)
VOICE_GENDER=FEMALE1
;;
esac
echo "AddVoice \"$LANG_FULL\" \"$VOICE_GENDER\" \"$VOICE\"" >> "$CONFIG_PATH"
done
echo "$PIPER_GENERIC_CONF" >> "$CONFIG_PATH"
echo "\033[1;36mDownload piper binaries\033[0m"
PIPER_BIN_GZ=$(mktemp)
curl -s -L -o "$PIPER_BIN_GZ" "$PIPER_BIN_GZ_URL"
rm -rf "$INSTALL_DIR/piper"
tar -xzf "$PIPER_BIN_GZ" -C "$INSTALL_DIR"
rm "$PIPER_BIN_GZ"
echo "\033[1;36mCreate check_piper_voice.sh\033[0m"
echo "$CHECK_PIPER_VOICE_SH" > "$INSTALL_DIR/check_piper_voice.sh"
chmod +x "$INSTALL_DIR/check_piper_voice.sh"
# ask if user wants to test piper
printf "\033[1;36mDo you want to test piper? [Y/n]\033[0m "
read -r YN </dev/tty
YN=${YN:-y}
case $YN in
[Yy]* )
echo "\033[1;36mPronouncing the following sentence:\033[0m \"Piper is now installed\""
spd-say -w -o piper-generic -y en_GB-alan-low "Piper is now installed"
;;
* ) ;;
esac
echo "\033[1;32mInstallation complete\033[0m"
@lamyergeier
Copy link

lamyergeier commented Jan 10, 2024

In the following snippet from above:

printf %s \'\$DATA\' \
| ./piper/piper --model \'voices/\$VOICE.onnx\' --output_raw \
| sox -v \$VOLUME -r \$(jq .audio.sample_rate < \'voices/\$VOICE.onnx.json\') -c 1 \
 -b 16 -e signed-integer -t raw - -t wav - tempo \$RATE pitch \$PITCH norm \
| \$PLAY_COMMAND"

I have a question - You have used VOLUME, RATE and PITCH variables. Are these standard variables from speech dispatcher?

@kc7dmf
Copy link

kc7dmf commented Jul 31, 2024

I appreciate the code, working on a different project, but it helped me with piper and sox.

@tgrushka
Copy link

Without adding the module to speechd.conf, it won't work.

Also, a major bug that still exists in spd-conf: it crashes when there is no config (why, oh why?). All we have to do is mkdir -p ~/.config/speech-dispatcher && touch ~/.config/speech-dispatcher/speechd.conf so it doesn't crash.

SPD_CONF_DIR=${SPD_CONF_DIR:-~/.config/speech-dispatcher}
CONFIG_DIR=${CONFIG_DIR:-$SPD_CONF_DIR/modules}

# ...

if ! [ -d "$CONFIG_DIR" ]; then
    echo "Config directory not found, executing spd-conf"
    # WORKAROUND BUG in spd-conf when speechd.conf does not exist
    mkdir -p $SPD_CONF_DIR && touch $SPD_CONF_DIR/speechd.conf
    spd-conf -n
fi

# ...

# Add module to speechd.conf
echo 'AddModule "piper" "sd_generic" "piper-generic.conf"' | tee -a $SPD_CONF_DIR/speechd.conf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment