You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
5.8 KiB
193 lines
5.8 KiB
#!/bin/bash |
|
# Script to download Piper TTS voices and place them in the correct structure |
|
# Voices are downloaded from Hugging Face: https://huggingface.co/rhasspy/piper-voices |
|
|
|
# Don't exit on error - we want to continue downloading other voices even if one fails |
|
set +e |
|
|
|
PIPER_DATA_DIR="./piper-data" |
|
VOICES_BASE_URL="https://huggingface.co/rhasspy/piper-voices/resolve/main" |
|
|
|
# Colors for output |
|
GREEN='\033[0;32m' |
|
YELLOW='\033[1;33m' |
|
NC='\033[0m' # No Color |
|
|
|
echo -e "${GREEN}Piper TTS Voice Downloader${NC}" |
|
echo "================================" |
|
echo "" |
|
|
|
# Create piper-data directory if it doesn't exist |
|
mkdir -p "$PIPER_DATA_DIR" |
|
|
|
# Function to download a voice |
|
download_voice() { |
|
local lang=$1 |
|
local locale=$2 |
|
local voice=$3 |
|
local quality=$4 |
|
|
|
local voice_name="${locale}-${voice}-${quality}" |
|
local voice_dir="${PIPER_DATA_DIR}/voices/${locale}/${voice}/${quality}" |
|
local onnx_file="${voice_dir}/${voice_name}.onnx" |
|
local json_file="${voice_dir}/${voice_name}.onnx.json" |
|
|
|
# Create directory structure |
|
mkdir -p "$voice_dir" |
|
|
|
# Check if voice already exists |
|
if [ -f "$onnx_file" ] && [ -f "$json_file" ]; then |
|
echo -e "${YELLOW}Voice ${voice_name} already exists, skipping...${NC}" |
|
return 0 |
|
fi |
|
|
|
echo "Downloading ${voice_name}..." |
|
|
|
# Download .onnx.json file |
|
local json_url="${VOICES_BASE_URL}/${lang}/${locale}/${voice}/${quality}/${voice_name}.onnx.json" |
|
local curl_output=$(curl -L -f -w "\n%{http_code}" -o "$json_file" "$json_url" 2>&1) |
|
local http_code=$(echo "$curl_output" | tail -n1) |
|
local curl_error=$(echo "$curl_output" | head -n-1) |
|
|
|
if [ "$http_code" = "200" ] && [ -f "$json_file" ] && [ -s "$json_file" ]; then |
|
echo " ✓ Downloaded ${voice_name}.onnx.json" |
|
else |
|
echo " ✗ Failed to download ${voice_name}.onnx.json" |
|
echo " URL: ${json_url}" |
|
echo " HTTP Code: ${http_code:-unknown}" |
|
if [ -n "$curl_error" ]; then |
|
echo " Error: $(echo "$curl_error" | head -n1)" |
|
fi |
|
echo " This quality level may not be available for this voice." |
|
rm -f "$json_file" |
|
return 1 |
|
fi |
|
|
|
# Download .onnx file |
|
local onnx_url="${VOICES_BASE_URL}/${lang}/${locale}/${voice}/${quality}/${voice_name}.onnx" |
|
curl_output=$(curl -L -f -w "\n%{http_code}" -o "$onnx_file" "$onnx_url" 2>&1) |
|
http_code=$(echo "$curl_output" | tail -n1) |
|
curl_error=$(echo "$curl_output" | head -n-1) |
|
|
|
if [ "$http_code" = "200" ] && [ -f "$onnx_file" ] && [ -s "$onnx_file" ]; then |
|
local file_size=$(stat -c%s "$onnx_file" 2>/dev/null || echo "0") |
|
local file_size_mb=$(echo "scale=2; $file_size / 1024 / 1024" | bc 2>/dev/null || echo "?") |
|
echo " ✓ Downloaded ${voice_name}.onnx (${file_size_mb} MB)" |
|
else |
|
echo " ✗ Failed to download ${voice_name}.onnx" |
|
echo " URL: ${onnx_url}" |
|
echo " HTTP Code: ${http_code:-unknown}" |
|
if [ -n "$curl_error" ]; then |
|
echo " Error: $(echo "$curl_error" | head -n1)" |
|
fi |
|
echo " This quality level may not be available for this voice." |
|
rm -f "$onnx_file" "$json_file" |
|
return 1 |
|
fi |
|
|
|
echo -e "${GREEN} ✓ Successfully downloaded ${voice_name}${NC}" |
|
return 0 |
|
} |
|
|
|
# List of voices to download (based on the language detection function) |
|
# Format: language_code locale voice quality |
|
VOICES=( |
|
# English (US) - all quality levels |
|
"en en_US lessac low" |
|
"en en_US lessac medium" |
|
"en en_US lessac high" |
|
# English (GB) |
|
"en en_GB alba medium" |
|
|
|
# German |
|
"de de_DE thorsten medium" |
|
"de de_DE thorsten low" |
|
|
|
# French |
|
"fr fr_FR siwis medium" |
|
"fr fr_FR siwis low" |
|
|
|
# Spanish |
|
"es es_ES davefx medium" |
|
# Note: es_ES-davefx-low doesn't exist |
|
|
|
# Italian - riccardo doesn't exist, removing |
|
# "it it_IT riccardo medium" - not available |
|
# "it it_IT riccardo low" - not available |
|
|
|
# Russian |
|
"ru ru_RU ruslan medium" |
|
# Note: ru_RU-ruslan-low doesn't exist |
|
|
|
# Chinese |
|
"zh zh_CN huayan medium" |
|
|
|
# Arabic - hafez doesn't exist, removing |
|
# "ar ar_SA hafez medium" - not available |
|
|
|
# Polish |
|
"pl pl_PL darkman medium" |
|
|
|
# Portuguese - edresson doesn't exist, removing |
|
# "pt pt_BR edresson medium" - not available |
|
|
|
# Dutch |
|
"nl nl_NL mls medium" |
|
|
|
# Czech |
|
"cs cs_CZ jirka medium" |
|
|
|
# Turkish |
|
"tr tr_TR dfki medium" |
|
|
|
# Japanese - nanami doesn't exist, removing |
|
# "ja ja_JP nanami medium" - not available |
|
|
|
# Korean - kyungha doesn't exist, removing |
|
# "ko ko_KR kyungha medium" - not available |
|
) |
|
|
|
# Check if specific voices are requested |
|
if [ $# -gt 0 ]; then |
|
VOICES=("$@") |
|
fi |
|
|
|
echo "Downloading ${#VOICES[@]} voice(s)..." |
|
echo "" |
|
|
|
SUCCESS=0 |
|
FAILED=0 |
|
|
|
for voice_spec in "${VOICES[@]}"; do |
|
# Parse voice specification |
|
read -r lang locale voice_name quality <<< "$voice_spec" |
|
|
|
# Validate voice specification |
|
if [ -z "$lang" ] || [ -z "$locale" ] || [ -z "$voice_name" ] || [ -z "$quality" ]; then |
|
echo -e "${YELLOW}⚠ Skipping invalid voice specification: '${voice_spec}'${NC}" |
|
((FAILED++)) |
|
echo "" |
|
continue |
|
fi |
|
|
|
if download_voice "$lang" "$locale" "$voice_name" "$quality"; then |
|
((SUCCESS++)) |
|
else |
|
((FAILED++)) |
|
fi |
|
echo "" |
|
done |
|
|
|
echo "================================" |
|
echo -e "${GREEN}Download complete!${NC}" |
|
echo "Successfully downloaded: $SUCCESS" |
|
if [ $FAILED -gt 0 ]; then |
|
echo -e "${YELLOW}Failed: $FAILED${NC}" |
|
fi |
|
echo "" |
|
echo "Voices are now in: $PIPER_DATA_DIR" |
|
echo "This directory is mounted into the Docker container at /data" |
|
echo "" |
|
echo "To use these voices, restart your Docker containers:" |
|
echo " docker-compose down" |
|
echo " docker-compose up --build"
|
|
|