Set-voice updated to new format. Also, search for and set voices using wine registry instead of old sed method.
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Set Voice
|
# Set Speech
|
||||||
# Set the default wine voice based on installed options.
|
# Configure SAPI voices and speech rate for wine32 bottle
|
||||||
|
|
||||||
#
|
# License header
|
||||||
# âe contents of this file are subject to the Common Public Attribution
|
# The contents of this file are subject to the Common Public Attribution
|
||||||
# License Version 1.0 (the âcenseâ you may not use this file except in
|
# License Version 1.0 (the "License"); you may not use this file except in
|
||||||
# compliance with the License. You may obtain a copy of the License at
|
# compliance with the License. You may obtain a copy of the License at
|
||||||
# https://opensource.org/licenses/CPAL-1.0. The License is based on the Mozilla Public License Version
|
# https://opensource.org/licenses/CPAL-1.0. The License is based on the Mozilla Public License Version
|
||||||
# 1.1 but Sections 14 and 15 have been added to cover use of software over a
|
# 1.1 but Sections 14 and 15 have been added to cover use of software over a
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
# Developer. In addition, Exhibit A has been modified to be consistent with
|
# Developer. In addition, Exhibit A has been modified to be consistent with
|
||||||
# Exhibit B.
|
# Exhibit B.
|
||||||
#
|
#
|
||||||
# Software distributed under the License is distributed on an â ISâasis,
|
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
# for the specific language governing rights and limitations under the
|
# for the specific language governing rights and limitations under the
|
||||||
# License.
|
# License.
|
||||||
@@ -40,8 +40,6 @@
|
|||||||
# Works which are defined in the CPAL as a work which combines Covered Code
|
# Works which are defined in the CPAL as a work which combines Covered Code
|
||||||
# or portions thereof with code not governed by the terms of the CPAL.
|
# or portions thereof with code not governed by the terms of the CPAL.
|
||||||
|
|
||||||
#--code--
|
|
||||||
|
|
||||||
# Detect dialog interface type BEFORE potentially setting DISPLAY
|
# Detect dialog interface type BEFORE potentially setting DISPLAY
|
||||||
if [[ -z "$DISPLAY" ]]; then
|
if [[ -z "$DISPLAY" ]]; then
|
||||||
dialogType="dialog"
|
dialogType="dialog"
|
||||||
@@ -52,274 +50,393 @@ fi
|
|||||||
# Source dialog interface wrapper
|
# Source dialog interface wrapper
|
||||||
source "${0%/*}/../.includes/dialog-interface.sh"
|
source "${0%/*}/../.includes/dialog-interface.sh"
|
||||||
|
|
||||||
|
# Settings to improve accessibility of dialog
|
||||||
|
export DIALOGOPTS='--insecure --no-lines --visit-items'
|
||||||
|
# Turn off debug messages
|
||||||
|
export WINEDEBUG="-all"
|
||||||
|
# Set DISPLAY if needed
|
||||||
|
if [[ -z "$DISPLAY" ]]; then
|
||||||
|
export DISPLAY=:0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set wine prefix to wine32 - this is the only supported bottle for SAPI
|
||||||
|
export WINEPREFIX="$HOME/.local/wine32"
|
||||||
|
export bottle="$HOME/.local/wine32"
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
echo "${0##*/}"
|
echo "${0##*/}"
|
||||||
echo "Released under the terms of the Common Public Attribution License Version 1.0"
|
echo "Released under the terms of the Common Public Attribution License Version 1.0"
|
||||||
echo -e "This is a Stormux project: https://stormux.org\n"
|
echo -e "This is a Stormux project: https://stormux.org\n"
|
||||||
echo -e "Usage:\n"
|
echo -e "Usage:\n"
|
||||||
echo "With no arguments, open the game launcher."
|
echo "With no arguments, open the voice configuration menu."
|
||||||
for i in "${!command[@]}" ; do
|
echo "-h: Show this help screen."
|
||||||
echo "-${i/:/ <parameter>}: ${command[${i}]}"
|
echo "-r <rate>: Set voice rate (0-10, where 10 is fastest)."
|
||||||
done | sort
|
echo "-v <voice>: Set voice by name."
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Set a variable to make mac compatibility easier...
|
|
||||||
sed="sed"
|
|
||||||
grep="grep"
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
sed="gsed"
|
|
||||||
grep="ggrep"
|
|
||||||
fi
|
|
||||||
export grep
|
|
||||||
export sed
|
|
||||||
# Settings to improve accessibility of dialog.
|
|
||||||
export DIALOGOPTS='--insecure --no-lines --visit-items'
|
|
||||||
# Turn off debug messages
|
|
||||||
export WINEDEBUG="-all"
|
|
||||||
# Set DISPLAY, as needed
|
|
||||||
if [ -z "$DISPLAY" ] ; then
|
|
||||||
export DISPLAY=:0
|
|
||||||
fi
|
|
||||||
# handle arguments
|
|
||||||
declare -A command=(
|
|
||||||
[b:]="the wine bottle to use."
|
|
||||||
[h]="This help screen."
|
|
||||||
[r:]="Specify voice rate, default is 7, options are 0-9 or A for fastest."
|
|
||||||
[v:]="Voice name, the voice to use, same as in the menu."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
msgbox() {
|
msgbox() {
|
||||||
# Returns: None
|
agm_msgbox "Set Speech" "Set Speech" "$*"
|
||||||
# Shows the provided message on the screen with an ok button.
|
|
||||||
agm_msgbox "Set Voice" "Set Voice" "$*"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
infobox() {
|
infobox() {
|
||||||
# Returns: None
|
|
||||||
# Shows the provided message on the screen with no buttons.
|
|
||||||
local timeout=3
|
local timeout=3
|
||||||
agm_infobox "Set Voice" "Set Voice" "$*"
|
agm_infobox "Set Speech" "Set Speech" "$*"
|
||||||
read -n1 -t $timeout continue
|
read -r -n1 -t $timeout
|
||||||
# Clear any keypresses from the buffer
|
read -r -t 0.01
|
||||||
read -t 0.01 continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yesno() {
|
yesno() {
|
||||||
# Returns: Yes or No
|
if agm_yesno "Set Speech" "Set Speech" "$*"; then
|
||||||
# Args: Question to user.
|
|
||||||
# Called in if $(yesno) == "Yes"
|
|
||||||
# Or variable=$(yesno)
|
|
||||||
agm_yesno "Set Voice" "Set Voice" "$*"
|
|
||||||
if [[ $? -eq 0 ]]; then
|
|
||||||
echo "Yes"
|
echo "Yes"
|
||||||
else
|
else
|
||||||
echo "No"
|
echo "No"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
menulist() {
|
voice_menu() {
|
||||||
# Args: List of items for menu.
|
declare -a menuList=()
|
||||||
# returns: selected tag
|
|
||||||
declare -a menuList
|
|
||||||
for i in "${@}"; do
|
for i in "${@}"; do
|
||||||
menuList+=("$i" "$i")
|
menuList+=("$i" "$i")
|
||||||
done
|
done
|
||||||
agm_menu "Set Voice" "Set Voice" "Please select one" "${menuList[@]}"
|
agm_menu "Set Speech" "Set Speech" "Please select a voice:" "${menuList[@]}"
|
||||||
return $?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_voice() {
|
rate_menu() {
|
||||||
if [[ $doRestore -eq 0 ]]; then
|
declare -a rateList=()
|
||||||
${wine}server -k # If we don't do this it's likely wine will overwrite our reverted change or even clobber the registry key entirely
|
for i in {0..10}; do
|
||||||
$sed -i -E -e 's/"DefaultTokenId"="HKEY_LOCAL_MACHINE\\\\(SOFTWARE|Software)\\\\(Wow6432Node\\\\|)Microsoft\\\\Speech\\\\Voices\\\\Token(Enum|)s\\\\[^"]+"/"DefaultTokenId"="'"${oldVoice//\\/\\\\}"'"/g' "${WINEPREFIX}/user.reg"
|
local desc="Rate $i"
|
||||||
|
if [[ $i -eq 0 ]]; then
|
||||||
|
desc="$desc (Slowest)"
|
||||||
|
elif [[ $i -eq 5 ]]; then
|
||||||
|
desc="$desc (Default)"
|
||||||
|
elif [[ $i -eq 10 ]]; then
|
||||||
|
desc="$desc (Fastest)"
|
||||||
|
fi
|
||||||
|
rateList+=("$i" "$desc")
|
||||||
|
done
|
||||||
|
agm_menu "Set Speech" "Set Speech" "Please select speech rate:" "${rateList[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_voices() {
|
||||||
|
# Get list of available voices using wine reg query
|
||||||
|
declare -a voices
|
||||||
|
declare -a voiceKeys
|
||||||
|
|
||||||
|
# First, get Microsoft SAPI voices
|
||||||
|
local allOutput
|
||||||
|
allOutput=$(WINEPREFIX="$WINEPREFIX" "$wine" reg query "HKLM\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens" /s 2>/dev/null)
|
||||||
|
|
||||||
|
# Parse output to find voice keys and names
|
||||||
|
local currentKey=""
|
||||||
|
local currentName=""
|
||||||
|
local inMainVoiceToken=false
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Clean up the line first
|
||||||
|
line=$(echo "$line" | tr -d '\r\n' | sed 's/[[:space:]]*$//')
|
||||||
|
|
||||||
|
# Check if this is a main voice token registry key line (exactly one level deep)
|
||||||
|
if [[ "$line" =~ ^HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\([^\\]+)$ ]]; then
|
||||||
|
# Save previous voice if we found a name and had collected both key and name
|
||||||
|
if [[ -n "$currentKey" && -n "$currentName" ]]; then
|
||||||
|
voices+=("$currentName")
|
||||||
|
voiceKeys+=("$currentKey")
|
||||||
|
fi
|
||||||
|
# Start new voice - clean up any carriage returns or whitespace
|
||||||
|
currentKey="${BASH_REMATCH[1]}"
|
||||||
|
currentName=""
|
||||||
|
inMainVoiceToken=true
|
||||||
|
# Check if this is a subkey (contains additional parts after token name)
|
||||||
|
elif [[ "$line" =~ ^HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\[^\\]+\\ ]]; then
|
||||||
|
# This is a subkey, not a main voice token
|
||||||
|
inMainVoiceToken=false
|
||||||
|
# Check if this is a default value line with voice name and we're in main token
|
||||||
|
elif [[ "$line" =~ ^[[:space:]]*\(Default\)[[:space:]]+REG_SZ[[:space:]]+(.+)$ ]] && [[ "$inMainVoiceToken" == true ]]; then
|
||||||
|
# Only capture voice name if we haven't already got one for this token
|
||||||
|
if [[ -z "$currentName" ]]; then
|
||||||
|
currentName="${BASH_REMATCH[1]}"
|
||||||
|
# Clean up the voice name as well
|
||||||
|
currentName=$(echo "$currentName" | tr -d '\r\n' | sed 's/[[:space:]]*$//')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$allOutput"
|
||||||
|
|
||||||
|
# Don't forget the last voice
|
||||||
|
if [[ -n "$currentKey" && -n "$currentName" ]]; then
|
||||||
|
voices+=("$currentName")
|
||||||
|
voiceKeys+=("$currentKey")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Now search for L&H TTS voices
|
||||||
|
local lhOutput
|
||||||
|
lhOutput=$(WINEPREFIX="$WINEPREFIX" "$wine" reg query "HKLM\\SOFTWARE\\L&H\\TTS\\V6.0\\Voice" /s 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$lhOutput" ]]; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Clean up the line first
|
||||||
|
line=$(echo "$line" | tr -d '\r\n' | sed 's/[[:space:]]*$//')
|
||||||
|
|
||||||
|
# Look for voice name entries like "Carol REG_SZ {227A0E40-A92A-11d1-B17B-0020AFED142E}"
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*([A-Za-z]+)[[:space:]]+REG_SZ[[:space:]]+\{([^}]+)\}$ ]]; then
|
||||||
|
local lhVoiceName="${BASH_REMATCH[1]}"
|
||||||
|
local lhVoiceGuid="${BASH_REMATCH[2]}"
|
||||||
|
|
||||||
|
# Add L&H prefix to distinguish from SAPI voices (escape ampersand for YAD)
|
||||||
|
voices+=("L&H $lhVoiceName")
|
||||||
|
voiceKeys+=("LH_$lhVoiceGuid")
|
||||||
|
fi
|
||||||
|
done <<< "$lhOutput"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#voices[@]} -eq 0 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export arrays for use in other functions
|
||||||
|
export voiceList=("${voices[@]}")
|
||||||
|
export voiceKeyList=("${voiceKeys[@]}")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_current_voice() {
|
||||||
|
# Get current default voice
|
||||||
|
local currentVoice
|
||||||
|
currentVoice=$(WINEPREFIX="$WINEPREFIX" "$wine" reg query "HKCU\\SOFTWARE\\Microsoft\\Speech\\Voices" /v "DefaultTokenId" 2>/dev/null | grep "REG_SZ" | sed 's/.*REG_SZ[[:space:]]*//')
|
||||||
|
|
||||||
|
if [[ -n "$currentVoice" ]]; then
|
||||||
|
# Extract just the voice key from the full registry path
|
||||||
|
# The path format is: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\MSMike
|
||||||
|
if [[ "$currentVoice" =~ Tokens\\([^\\]+)$ ]]; then
|
||||||
|
local voiceKey="${BASH_REMATCH[1]}"
|
||||||
|
# Clean up any whitespace or special characters
|
||||||
|
voiceKey=$(echo "$voiceKey" | tr -d '\r\n' | sed 's/[[:space:]]*$//')
|
||||||
|
echo "$voiceKey"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_current_rate() {
|
||||||
|
# Get current speech rate
|
||||||
|
local currentRate
|
||||||
|
currentRate=$(WINEPREFIX="$WINEPREFIX" "$wine" reg query "HKCU\\SOFTWARE\\Microsoft\\Speech\\Voices" /v "DefaultTTSRate" 2>/dev/null | grep "REG_DWORD" | sed 's/.*REG_DWORD[[:space:]]*//' | sed 's/^0x//')
|
||||||
|
|
||||||
|
if [[ -n "$currentRate" && "$currentRate" =~ ^[0-9a-fA-F]+$ ]]; then
|
||||||
|
# Convert hex to decimal
|
||||||
|
echo $((16#$currentRate))
|
||||||
|
else
|
||||||
|
echo "5" # Default rate
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
set_voice() {
|
set_voice() {
|
||||||
doRestore=1
|
local voiceName="$1"
|
||||||
local tmp="$1"
|
local voiceKey=""
|
||||||
local fullVoice
|
|
||||||
local counter=0
|
# Find the voice key for the given voice name
|
||||||
for x in "${voiceList[@]}" ; do
|
local i
|
||||||
[[ "$x" = "$tmp" ]] && break
|
for i in "${!voiceList[@]}"; do
|
||||||
counter=$(( $counter + 1 ))
|
if [[ "${voiceList[i]}" == "$voiceName" ]]; then
|
||||||
|
voiceKey="${voiceKeyList[i]}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
local RHVoiceName=""
|
|
||||||
local RHVoicePath="${WINEPREFIX}/drive_c/ProgramData/Olga Yakovleva/RHVoice/data/voices/"
|
if [[ -z "$voiceKey" ]]; then
|
||||||
if [[ -d "$RHVoicePath" ]]; then
|
msgbox "Error: Voice '$voiceName' not found."
|
||||||
RHVoiceName="$(find "$RHVoicePath" -maxdepth 1 -type d -not -path "$RHVoicePath" | head -1)"
|
return 1
|
||||||
if [[ -n "$RHVoiceName" ]]; then
|
|
||||||
RHVoiceName="${RHVoiceName##*/}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if this is an L&H voice (cannot be set as system default)
|
||||||
|
if [[ "$voiceKey" =~ ^LH_ ]]; then
|
||||||
|
msgbox "L&H voices cannot be set as the system default SAPI voice. They use a proprietary API and must be selected by individual applications that support L&H TTS."
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
fullVoice="${voiceListFullName[$counter]}"
|
|
||||||
if [[ -n "$RHVoiceName" ]]; then
|
# Kill wine server to ensure registry changes take effect
|
||||||
fullVoice="${fullVoice/RHVoice/RHVoice\\\\${RHVoiceName}}"
|
WINEPREFIX="$WINEPREFIX" "$wineserver" -k 2>/dev/null
|
||||||
|
|
||||||
|
# Set the default voice using wine reg
|
||||||
|
local fullVoicePath="HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\$voiceKey"
|
||||||
|
if WINEPREFIX="$WINEPREFIX" "$wine" reg add "HKCU\\SOFTWARE\\Microsoft\\Speech\\Voices" /v "DefaultTokenId" /t REG_SZ /d "$fullVoicePath" /f 2>/dev/null; then
|
||||||
|
infobox "Voice set to: $voiceName"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
msgbox "Error: Failed to set voice."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_rate() {
|
||||||
|
local rate="$1"
|
||||||
|
|
||||||
|
# Validate rate (0-10)
|
||||||
|
if ! [[ "$rate" =~ ^[0-9]$|^10$ ]]; then
|
||||||
|
msgbox "Error: Rate must be between 0 and 10."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kill wine server to ensure registry changes take effect
|
||||||
|
WINEPREFIX="$WINEPREFIX" "$wineserver" -k 2>/dev/null
|
||||||
|
|
||||||
|
# Set the speech rate using wine reg (convert to hex)
|
||||||
|
local hexRate
|
||||||
|
hexRate=$(printf "%x" "$rate")
|
||||||
|
if WINEPREFIX="$WINEPREFIX" "$wine" reg add "HKCU\\SOFTWARE\\Microsoft\\Speech\\Voices" /v "DefaultTTSRate" /t REG_DWORD /d "0x$hexRate" /f 2>/dev/null; then
|
||||||
|
infobox "Speech rate set to: $rate"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
msgbox "Error: Failed to set speech rate."
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
${wine}server -k # If we don't do this it's likely wine will overwrite our reverted change or even clobber the registry key entirely
|
|
||||||
# Remove any existing rate change for voices
|
|
||||||
$sed -i '/"DefaultTTSRate"=dword:/d' "${WINEPREFIX}/user.reg"
|
|
||||||
$sed -i -E -e 's/"DefaultTokenId"="HKEY_LOCAL_MACHINE\\\\(SOFTWARE|Software)\\\\(Wow6432Node\\\\|)Microsoft\\\\Speech\\\\Voices\\\\Token(Enum|)s\\\\[^"]+"/"DefaultTokenId"="HKEY_LOCAL_MACHINE\\\\'"${fullVoice//\\/\\\\}"'"\n"DefaultTTSRate"=dword:0000000'${rate:-7}'/g' "${WINEPREFIX}/user.reg"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test_voice() {
|
test_voice() {
|
||||||
doRestore=0
|
local voiceName="$1"
|
||||||
local tmp="$1"
|
|
||||||
local fullVoice
|
# Set the voice temporarily for testing
|
||||||
local counter=0
|
set_voice "$voiceName" || return 1
|
||||||
for x in "${voiceList[@]}" ; do
|
|
||||||
[ "$x" = "$tmp" ] && break
|
# Create test script
|
||||||
counter=$(( $counter + 1 ))
|
mkdir -p "${WINEPREFIX}/drive_c/windows/temp"
|
||||||
done
|
|
||||||
local RHVoiceName=""
|
|
||||||
local RHVoicePath="${WINEPREFIX}/drive_c/ProgramData/Olga Yakovleva/RHVoice/data/voices/"
|
|
||||||
if [[ -d "$RHVoicePath" ]]; then
|
|
||||||
RHVoiceName="$(find "$RHVoicePath" -maxdepth 1 -type d -not -path "$RHVoicePath" | head -1)"
|
|
||||||
if [[ -n "$RHVoiceName" ]]; then
|
|
||||||
RHVoiceName="${RHVoiceName##*/}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fullVoice="${voiceListFullName[$counter]}"
|
|
||||||
if [[ -n "$RHVoiceName" ]]; then
|
|
||||||
fullVoice="${fullVoice/RHVoice/RHVoice\\\\${RHVoiceName}}"
|
|
||||||
fi
|
|
||||||
${wine}server -k # If we don't do this it's likely wine will overwrite our reverted change or even clobber the registry key entirely
|
|
||||||
$sed -i -E -e 's/"DefaultTokenId"="HKEY_LOCAL_MACHINE\\\\(SOFTWARE|Software)\\\\(Wow6432Node\\\\|)Microsoft\\\\Speech\\\\Voices\\\\Token(Enum|)s\\\\[^"]+"/"DefaultTokenId"="HKEY_LOCAL_MACHINE\\\\'"${fullVoice//\\/\\\\}"'"/g' "${WINEPREFIX}/user.reg"
|
|
||||||
cat << "EOF" > "${WINEPREFIX}/drive_c/windows/temp/speak.vbs"
|
cat << "EOF" > "${WINEPREFIX}/drive_c/windows/temp/speak.vbs"
|
||||||
dim speechobject
|
dim speechObject
|
||||||
set speechobject=createobject("sapi.spvoice")
|
set speechObject = createObject("sapi.spvoice")
|
||||||
speechobject.speak "This is a test of your chosen voice. It contains multiple sentences and punctuation, and is designed to give a full representation of this voices qualities."
|
speechObject.speak "This is a test of your chosen voice. It contains multiple sentences and punctuation, and is designed to give a full representation of this voice's qualities."
|
||||||
EOF
|
EOF
|
||||||
${wine} cscript "c:\windows\temp\speak.vbs"
|
|
||||||
|
# Test the voice
|
||||||
|
WINEPREFIX="$WINEPREFIX" "$wine" cscript "c:\\windows\\temp\\speak.vbs" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Handle voice restore, but only if voice changed
|
initialize_sapi() {
|
||||||
doRestore=1
|
# Initialize SAPI if not already done
|
||||||
trap restore_voice SIGINT
|
mkdir -p "${WINEPREFIX}/drive_c/windows/temp"
|
||||||
|
cat << "EOF" > "${WINEPREFIX}/drive_c/windows/temp/init.vbs"
|
||||||
|
dim speechObject
|
||||||
|
set speechObject = createObject("sapi.spvoice")
|
||||||
|
speechObject.speak ""
|
||||||
|
EOF
|
||||||
|
WINEPREFIX="$WINEPREFIX" "$wine" cscript "c:\\windows\\temp\\init.vbs" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
# Convert the keys of the command associative array to a format usable by getopts
|
# Handle command line arguments
|
||||||
args="${!command[*]}"
|
while getopts "hr:v:" option; do
|
||||||
args="${args//[[:space:]]/}"
|
case "$option" in
|
||||||
while getopts "${args}" i ; do
|
|
||||||
case "$i" in
|
|
||||||
b)
|
|
||||||
# Check for valid bottles in all possible locations
|
|
||||||
if [[ -d ~/.local/wine/${OPTARG} ]]; then
|
|
||||||
export bottle=~/.local/wine/${OPTARG}
|
|
||||||
export WINEPREFIX=~/.local/wine/${OPTARG}
|
|
||||||
elif [[ -d ~/.local/wine32 && ${OPTARG} == "wine32" ]]; then
|
|
||||||
export bottle=~/.local/wine32
|
|
||||||
export WINEPREFIX=~/.local/wine32
|
|
||||||
elif [[ -d ~/.local/wine64 && ${OPTARG} == "wine64" ]]; then
|
|
||||||
export bottle=~/.local/wine64
|
|
||||||
export WINEPREFIX=~/.local/wine64
|
|
||||||
else
|
|
||||||
echo "Invalid wine bottle specified."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
h) help ;;
|
h) help ;;
|
||||||
r)
|
r)
|
||||||
if ! [[ "${OPTARG}" =~ ^[0-9A]$ ]]; then
|
if ! [[ "$OPTARG" =~ ^[0-9]$|^10$ ]]; then
|
||||||
echo "Invalid rate specified. Arguments must be 0-9 or A."
|
echo "Error: Rate must be between 0 and 10."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
rate="${OPTARG}"
|
requestedRate="$OPTARG"
|
||||||
;;
|
;;
|
||||||
v) voice="${OPTARG}";;
|
v) requestedVoice="$OPTARG" ;;
|
||||||
|
*) help ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Check if wine32 bottle exists
|
||||||
# Get the desired wine bottle
|
if [[ ! -d "$WINEPREFIX" ]]; then
|
||||||
|
msgbox "Error: Wine32 bottle not found at $WINEPREFIX. Please create it first."
|
||||||
# Offer a list of wine bottles if one isn't specified on the command line.
|
|
||||||
if [[ -z "${bottle}" ]]; then
|
|
||||||
declare -a bottles=()
|
|
||||||
|
|
||||||
# Check for wine32 and wine64 bottles
|
|
||||||
[[ -d ~/.local/wine32 ]] && bottles+=("$HOME/.local/wine32" "wine32")
|
|
||||||
[[ -d ~/.local/wine64 ]] && bottles+=("$HOME/.local/wine64" "wine64")
|
|
||||||
|
|
||||||
# Add bottles from ~/.local/wine directory
|
|
||||||
if [[ -d ~/.local/wine ]]; then
|
|
||||||
for i in $(find ~/.local/wine/ -maxdepth 1 -type d -not -name 'wine' | sort); do
|
|
||||||
bottles+=("$i" "${i##*/}")
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#bottles[@]} -eq 0 ]]; then
|
|
||||||
echo "No wine bottles found in ~/.local/wine32, ~/.local/wine64, or ~/.local/wine/"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export WINEPREFIX="$(agm_menu "Set Voice" "Set Voice" "Select A Wine Bottle" "${bottles[@]}")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${WINEPREFIX}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get wine version if available
|
# Get wine version if available
|
||||||
if [[ -r "${WINEPREFIX}/agm.conf" ]]; then
|
if [[ -r "${WINEPREFIX}/agm.conf" ]]; then
|
||||||
source "${WINEPREFIX}/agm.conf"
|
source "${WINEPREFIX}/agm.conf"
|
||||||
export WINE
|
export WINE
|
||||||
export WINESERVER
|
export WINESERVER
|
||||||
fi
|
fi
|
||||||
wine="${WINE:-$(command -v wine)}"
|
wine="${WINE:-$HOME/.local/share/audiogame-manager/wine32/bin/wine}"
|
||||||
wineserver="${WINESERVER:-$(command -v wineserver)}"
|
wineserver="${WINESERVER:-$HOME/.local/share/audiogame-manager/wine32/bin/wineserver}"
|
||||||
|
|
||||||
# Debug information - comment out or delete when not needed
|
# Check if wine executable exists
|
||||||
# echo "Using wine bottle: ${WINEPREFIX}"
|
if [[ ! -x "$wine" ]]; then
|
||||||
# echo "Wine executable: ${wine}"
|
msgbox "Error: Wine executable not found at $wine"
|
||||||
# echo "Wineserver executable: ${wineserver}"
|
|
||||||
|
|
||||||
# In case the user hasn't run a game using sapi in this prefix yet, let's try to initialize all the registry keys properly.
|
|
||||||
mkdir -p "${WINEPREFIX}/drive_c/windows/temp"
|
|
||||||
cat << "EOF" > "${WINEPREFIX}/drive_c/windows/temp/speak.vbs"
|
|
||||||
dim speechobject
|
|
||||||
set speechobject=createobject("sapi.spvoice")
|
|
||||||
speechobject.speak ""
|
|
||||||
EOF
|
|
||||||
${wine} cscript "c:\windows\temp\speak.vbs"
|
|
||||||
|
|
||||||
# Create an array of available voices.
|
|
||||||
ifs="$IFS"
|
|
||||||
IFS=$'\n'
|
|
||||||
voiceListFullName=($($grep -P '\[Software\\\\(Wow6432Node\\\\|)Microsoft\\\\Speech\\\\Voices\\\\Token(Enum|)s\\\\[^\\]+\].*' "${WINEPREFIX}/system.reg" | $sed -E -e 's/\[([^]]+)\].*/\1/g'))
|
|
||||||
IFS="$ifs"
|
|
||||||
|
|
||||||
# Debug the voice list - comment out or delete when not needed
|
|
||||||
# echo "Found ${#voiceListFullName[@]} voices in registry"
|
|
||||||
# for voice in "${voiceListFullName[@]}"; do
|
|
||||||
# echo "Voice: $voice"
|
|
||||||
# done
|
|
||||||
|
|
||||||
voiceList=()
|
|
||||||
for x in "${voiceListFullName[@]}" ; do
|
|
||||||
voiceList+=("$(echo "$x" | $sed -E -e 's/Software\\\\(Wow6432Node\\\\|)Microsoft\\\\Speech\\\\Voices\\\\Token(Enum|)s\\\\(.+)/\3/g')")
|
|
||||||
done
|
|
||||||
oldVoice="$($grep -P '"DefaultTokenId"="HKEY_LOCAL_MACHINE\\\\(SOFTWARE|Software)\\\\(Wow6432Node\\\\|)Microsoft\\\\Speech\\\\Voices\\\\Token(Enum|)s\\\\[^"]+"' "${WINEPREFIX}/user.reg" | $sed -E -e 's/"DefaultTokenId"="([^"]+)"/\1/g')"
|
|
||||||
exit=1
|
|
||||||
if [[ "${#voiceList[@]}" -eq 0 ]]; then
|
|
||||||
agm_msgbox "Set Voice" "Set Voice" "No voices found in ${WINEPREFIX}. Make sure SAPI voices are installed in this Wine prefix."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
while [[ $exit -ne 0 ]] ; do
|
|
||||||
if [[ -z "${voice}" ]]; then
|
# Initialize SAPI
|
||||||
voice="$(menulist "${voiceList[@]}")"
|
initialize_sapi
|
||||||
|
|
||||||
|
# Get available voices
|
||||||
|
if ! get_voices; then
|
||||||
|
msgbox "No SAPI voices found in wine32 bottle. Please install SAPI voices first."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case $? in
|
# Handle command line options
|
||||||
0)
|
if [[ -n "$requestedRate" ]]; then
|
||||||
set_voice "$voice" "${rate:-7}" ; exit=0 ;;
|
set_rate "$requestedRate"
|
||||||
3)
|
fi
|
||||||
test_voice "$voice";;
|
|
||||||
*)
|
if [[ -n "$requestedVoice" ]]; then
|
||||||
restore_voice ; exit=0 ;;
|
set_voice "$requestedVoice"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show interactive menu if no voice specified
|
||||||
|
currentVoice=$(get_current_voice)
|
||||||
|
currentRate=$(get_current_rate)
|
||||||
|
|
||||||
|
# Verify arrays are properly set
|
||||||
|
if [[ ${#voiceList[@]} -eq 0 ]]; then
|
||||||
|
msgbox "Error: No voices loaded."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Build menu with current settings
|
||||||
|
declare -a menuOptions=()
|
||||||
|
|
||||||
|
# Find current voice name for display
|
||||||
|
currentVoiceName="None"
|
||||||
|
if [[ -n "$currentVoice" ]]; then
|
||||||
|
for i in "${!voiceKeyList[@]}"; do
|
||||||
|
# Clean up the voice key for comparison
|
||||||
|
cleanKey=$(echo "${voiceKeyList[i]}" | tr -d '\r\n' | sed 's/[[:space:]]*$//')
|
||||||
|
if [[ "$cleanKey" == "$currentVoice" ]]; then
|
||||||
|
currentVoiceName="${voiceList[i]}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
menuOptions+=("voice" "Select Voice (Current: $currentVoiceName)")
|
||||||
|
menuOptions+=("rate" "Set Rate (Current: $currentRate)")
|
||||||
|
menuOptions+=("test" "Test Current Voice")
|
||||||
|
menuOptions+=("quit" "Exit")
|
||||||
|
|
||||||
|
choice=$(agm_menu "Set Speech" "SAPI Voice Configuration" "Choose an option:" "${menuOptions[@]}")
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
"voice")
|
||||||
|
if selectedVoice=$(voice_menu "${voiceList[@]}"); then
|
||||||
|
if [[ -n "$selectedVoice" ]] && set_voice "$selectedVoice"; then
|
||||||
|
currentVoice=$(get_current_voice)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"rate")
|
||||||
|
if newRate=$(rate_menu); then
|
||||||
|
if [[ -n "$newRate" ]] && set_rate "$newRate"; then
|
||||||
|
currentRate=$(get_current_rate)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"test")
|
||||||
|
if [[ -n "$currentVoice" && "$currentVoiceName" != "None" ]]; then
|
||||||
|
infobox "Testing voice: $currentVoiceName"
|
||||||
|
test_voice "$currentVoiceName"
|
||||||
|
else
|
||||||
|
msgbox "No voice selected. Please select a voice first."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"quit"|"")
|
||||||
|
break
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user