"Changes for Basque language"

pull/2961/head
Igor Leturia 2022-06-15 18:49:20 +02:00
parent 5071f38dab
commit 7221bbf486
127 changed files with 1535 additions and 629 deletions

3
.shellcheckrc Normal file
View File

@ -0,0 +1,3 @@
# Disable sourcing errors
disable=SC1090
disable=SC1091

2
Jenkinsfile vendored
View File

@ -14,7 +14,7 @@ pipeline {
}
environment {
//spawns GITHUB_USR and GITHUB_PSW environment variables
GITHUB=credentials('38b2e4a6-167a-40b2-be6f-d69be42c8190')
GITHUB=credentials('DevOps-CLA-Checker-Github-Key')
}
steps {
// Using an install of Github repo CLA tagger

View File

@ -21,4 +21,4 @@ DIR="$( dirname "$SOURCE" )"
source "$DIR/../venv-activate.sh" -q
# Invoke the Command Line Interface
python -m mycroft.client.text $@
python -m mycroft.client.text "$@"

View File

@ -15,7 +15,7 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"
cd -P "$( dirname "$SOURCE" )" || exit
DIR="$( pwd )"
script=${0}
script=${script##*/}
@ -49,7 +49,7 @@ function help() {
VIEWER="nano --syntax=json --view"
if [ -z "$EDITOR" ] ; then
if [ $( which sensible-editor ) ] ; then
if which sensible-editor > /dev/null ; then
EDITOR="sensible-editor"
else
EDITOR="nano --syntax=json --tempfile"
@ -65,11 +65,17 @@ function found_exe() {
}
if found_exe tput ; then
# shellcheck disable=SC2034
GREEN="$(tput setaf 2)"
# shellcheck disable=SC2034
BLUE="$(tput setaf 4)"
# shellcheck disable=SC2034
CYAN="$(tput setaf 6)"
# shellcheck disable=SC2034
YELLOW="$(tput setaf 3)"
# shellcheck disable=SC2034
RESET="$(tput sgr0)"
# shellcheck disable=SC2034
HIGHLIGHT=${YELLOW}
fi
@ -82,14 +88,14 @@ function validate_config_file() {
return 0
fi
echo -n ${BLUE}
echo -n "${BLUE}"
# Remove any comments (lines starting with # or //) found in the file and
# Use jq to validate and output errors
sed 's/^\s*[#\/].*$//g' "$1" | sed '/^$/d' | jq -e "." > /dev/null
result=$?
echo -n ${RESET}
echo -n "${RESET}"
#xxx echo "RESULT=$result for $1"
return $result
@ -99,7 +105,7 @@ _conf_file="${XDG_CONFIG_HOME:-$HOME/.config}/mycroft/mycroft.conf"
function name_to_path() {
case ${1} in
"system") _conf_file="/etc/mycroft/mycroft.conf" ;;
"user") _conf_file=$(readlink -f ${XDG_CONFIG_HOME:-$HOME/.config}/mycroft/mycroft.conf) ;;
"user") _conf_file=$(readlink -f "${XDG_CONFIG_HOME:-$HOME/.config}/mycroft/mycroft.conf") ;;
"default") _conf_file="$DIR/../mycroft/configuration/mycroft.conf" ;;
"remote") _conf_file="$HOME/.cache/mycroft/web_cache.json" ;;
@ -113,12 +119,12 @@ function name_to_path() {
################################################################
function edit_config() {
name_to_path $1
validate_config_file $_conf_file
name_to_path "$1"
validate_config_file "$_conf_file"
rc=$?
if [ $rc -ne 0 ] ; then
echo "${YELLOW}WARNING: ${RESET}Configuration file did not pass validation before edits."
read -p "Review errors above and press ENTER to continue with editing."
read -r -p "Review errors above and press ENTER to continue with editing."
fi
if [ -f "${_conf_file}" ] ; then
@ -128,7 +134,7 @@ function edit_config() {
echo "}" >> "${TEMP}/mycroft.json"
fi
while [ 1 ] ; do
while true ; do
case $1 in
system | user)
# Allow user to edit
@ -150,19 +156,18 @@ function edit_config() {
fi
# file was changed, validate changes
validate_config_file $TEMP/mycroft.json
if [ $? -ne 0 ] ; then
if validate_config_file $TEMP/mycroft.json > /dev/null ; then
key="S"
else
echo "${YELLOW}WARNING: ${RESET}Configuration file does not pass validation, see errors above."
echo "Press X to abandon changes, S to force save, any other key to edit again."
read -N1 -s key
else
key="S"
read -r -N1 -s key
fi
case $key in
[Ss])
echo "Saving..."
mv $TEMP/mycroft.json $_conf_file
mv $TEMP/mycroft.json "$_conf_file"
signal_reload_config
break
;;
@ -180,11 +185,11 @@ function signal_reload_config() {
source "$DIR/../venv-activate.sh" -q
# Post a messagebus notification to reload the config file
output=$(python -m mycroft.messagebus.send "configuration.updated" "{}")
python -m mycroft.messagebus.send "configuration.updated" "{}" > /dev/null
}
function show_config() {
name_to_path $1
name_to_path "$1"
# Use jq to display formatted nicely (after stripping out comments)
sed 's/^\s*[#\/].*$//g' "${_conf_file}" | sed '/^$/d' | jq "."
@ -201,7 +206,7 @@ function get_config() {
json_config=$( source "$DIR/../venv-activate.sh" -q && python -c "import json; from mycroft.configuration import Configuration; print(json.dumps(Configuration.get()))" )
# Read the given variable from the mix
echo ${json_config} | jq -r "${value}"
echo "${json_config}" | jq -r "${value}"
}
function set_config() {
@ -212,10 +217,9 @@ function set_config() {
value=".${value}"
fi
jq "${value} = \"$2\"" ~/.mycroft/mycroft.conf > "${TEMP}/~mycroft.conf"
if [ $? -eq 0 ] ; then
if jq "${value} = \"$2\"" "$_conf_file" > "${TEMP}/~mycroft.conf" ; then
# Successful update, replace the config file
mv "${TEMP}/~mycroft.conf" ~/.mycroft/mycroft.conf
mv "${TEMP}/~mycroft.conf" "$_conf_file"
signal_reload_config
fi
}
@ -223,16 +227,16 @@ function set_config() {
_opt=$1
case ${_opt} in
"edit")
edit_config $2
edit_config "$2"
;;
"reload")
signal_reload_config
;;
"show")
show_config $2
show_config "$2"
;;
"get")
get_config $2
get_config "$2"
;;
"set")
set_config "$2" "$3"

View File

@ -15,10 +15,10 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"/..
cd -P "$( dirname "$SOURCE" )"/.. || exit
DIR="$( pwd )"
echo -e "\e[36mMycroft\e[0m is your open source voice assistant. Full source"
echo -e "\\e[36mMycroft\\e[0m is your open source voice assistant. Full source"
echo "can be found at: ${DIR}"
echo
echo "Mycroft-specific commands you can use from the Linux command prompt:"

View File

@ -15,7 +15,7 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"
cd -P "$( dirname "$SOURCE" )" || exit
DIR="$( pwd )"
# Enter the Mycroft venv

View File

@ -15,7 +15,7 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"
cd -P "$( dirname "$SOURCE" )" || exit
DIR="$( pwd )"
restart=0
@ -32,7 +32,7 @@ fi
# Launch the standard audiotest
"$DIR/../start-mycroft.sh" audiotest $@
"$DIR/../start-mycroft.sh" audiotest "$@"
if [ $restart -eq 1 ]

View File

@ -21,4 +21,4 @@ DIR="$( dirname "$SOURCE" )"
source "$DIR/../venv-activate.sh" -q
# Invoke the Mycroft Skills Kit from within the venv
msk $@
msk "$@"

View File

@ -21,4 +21,4 @@ DIR="$( dirname "$SOURCE" )"
source "$DIR/../venv-activate.sh" -q
# Invoke the Mycroft Skills Manager (msm) within the venv
msm $@
msm "$@"

View File

@ -21,4 +21,4 @@ DIR="$( dirname "$SOURCE" )"
source "$DIR/../venv-activate.sh" -q
# Install pip packages within the Mycroft venv
pip $@
pip "$@"

View File

@ -15,7 +15,7 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"
cd -P "$( dirname "$SOURCE" )" || exit
DIR="$( pwd )"
# Enter the Mycroft venv
@ -25,5 +25,5 @@ source "$DIR/../venv-activate.sh" -q
set -- "${1:-$(</dev/stdin)}" "${@:2}"
# Send a message to be spoken
data="$@"
output=$(python -m mycroft.messagebus.send "recognizer_loop:utterance" "{\"utterances\": [\"$data\"], \"lang\": \"en-us\"}")
data="$*"
python -m mycroft.messagebus.send "recognizer_loop:utterance" "{\"utterances\": [\"$data\"], \"lang\": \"en-us\"}" >> /dev/null

View File

@ -20,26 +20,31 @@ DIR="$( dirname "$SOURCE" )"
# Enter the Mycroft venv
source "$DIR/../venv-activate.sh" -q
function count-files() {
# shellcheck disable=SC2012
ls "$1" | wc -l
}
function vktest-clear() {
FEATURES_DIR="$DIR/../test/integrationtests/voight_kampff/features"
num_feature_files=$(ls $FEATURES_DIR | wc -l)
num_feature_files=$(count-files "$FEATURES_DIR")
# A clean directory will have `steps/` and `environment.py`
if [ $num_feature_files -gt "2" ] ; then
if [ "$num_feature_files" -gt "2" ] ; then
echo "Removing Feature files..."
rm ${DIR}/../test/integrationtests/voight_kampff/features/*.feature
rm ${DIR}/../test/integrationtests/voight_kampff/features/*.config.json
rm -f "${DIR}"/../test/integrationtests/voight_kampff/features/*.feature
rm -f "${DIR}"/../test/integrationtests/voight_kampff/features/*.config.json
fi
STEPS_DIR="$FEATURES_DIR/steps"
num_steps_files=$(ls $STEPS_DIR | wc -l)
if [ $num_steps_files -gt "2" ] ; then
num_steps_files=$(count-files "$STEPS_DIR")
if [ "$num_steps_files" -gt "2" ] ; then
echo "Removing Custom Step files..."
TMP_DIR="$STEPS_DIR/tmp"
mkdir $TMP_DIR
mv "$STEPS_DIR/configuration.py" $TMP_DIR
mv "$STEPS_DIR/utterance_responses.py" $TMP_DIR
rm ${STEPS_DIR}/*.py
mv ${TMP_DIR}/* $STEPS_DIR
rmdir $TMP_DIR
mkdir "$TMP_DIR"
mv "$STEPS_DIR/configuration.py" "$TMP_DIR"
mv "$STEPS_DIR/utterance_responses.py" "$TMP_DIR"
rm -f "${STEPS_DIR}"/*.py
mv "${TMP_DIR}"/* "$STEPS_DIR"
rmdir "$TMP_DIR"
fi
echo "Voight Kampff tests clear."
}
@ -55,5 +60,5 @@ elif [ "$1" = "vktest" ] ; then
python -m test.integrationtests.voight_kampff "$@"
fi
else
python -m test.integrationtests.skills.runner $@
python -m test.integrationtests.skills.runner "$@"
fi

View File

@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"
cd -P "$( dirname "$SOURCE" )" || exit
DIR="$( pwd )"
# Sets var 1 to stdin if no args were given
@ -23,5 +23,5 @@ set -- "${1:-$(</dev/stdin)}" "${@:2}"
source "$DIR/../venv-activate.sh" -q
# Send a message to be spoken
data="$@"
output=$(python -m mycroft.messagebus.send "speak" "{\"utterance\": \"$data\"}")
data="$*"
python -m mycroft.messagebus.send "speak" "{\"utterance\": \"$data\"}" >> /dev/null

View File

@ -15,7 +15,7 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"/..
cd -P "$( dirname "$SOURCE" )"/.. || exit
DIR="$( pwd )"
. "$DIR/start-mycroft.sh" $@
. "$DIR/start-mycroft.sh" "$@"

View File

@ -15,7 +15,7 @@
# limitations under the License.
SOURCE="${BASH_SOURCE[0]}"
cd -P "$( dirname "$SOURCE" )"/..
cd -P "$( dirname "$SOURCE" )"/.. || exit
DIR="$( pwd )"
. "$DIR/stop-mycroft.sh" $@
. "$DIR/stop-mycroft.sh" "$@"

View File

@ -22,22 +22,28 @@ export LANGUAGE=en
# exit on any error
set -Ee
cd $(dirname $0)
ROOT_DIRNAME=$(dirname "$0")
cd "$ROOT_DIRNAME"
TOP=$(pwd -L)
function clean_mycroft_files() {
echo '
This will completely remove any files installed by mycroft (including pairing
information).
information).
NOTE: This will not remove Mimic (if you chose to compile it), or other files
generated within the mycroft-core directory.
Do you wish to continue? (y/n)'
while true; do
read -N1 -s key
read -rN1 -s key
case $key in
[Yy])
sudo rm -rf /var/log/mycroft
rm -f /var/tmp/mycroft_web_cache.json
rm -rf "${TMPDIR:-/tmp}/mycroft"
rm -rf "$HOME/.mycroft"
rm -f "skills" # The Skills directory symlink
sudo rm -rf "/opt/mycroft"
exit 0
;;
@ -70,6 +76,7 @@ opt_forcemimicbuild=false
opt_allowroot=false
opt_skipmimicbuild=false
opt_python=python3
disable_precise_later=false
param=''
for var in "$@" ; do
@ -140,7 +147,7 @@ function get_YN() {
# Loop until the user hits the Y or the N key
echo -e -n "Choice [${CYAN}Y${RESET}/${CYAN}N${RESET}]: "
while true; do
read -N1 -s key
read -rN1 -s key
case $key in
[Yy])
return 0
@ -176,8 +183,8 @@ your environment.'
sleep 0.5
# The AVX instruction set is an x86 construct
# ARM has a range of equivalents, unsure which are (un)supported by TF.
if ! grep -q avx /proc/cpuinfo && [[ ! $(uname -m) == 'arm'* ]]; then
echo "
if ! grep -q avx /proc/cpuinfo && ! [[ $(uname -m) == 'arm'* || $(uname -m) == 'aarch64' ]]; then
echo "
The Precise Wake Word Engine requires the AVX instruction set, which is
not supported on your CPU. Do you want to fall back to the PocketSphinx
engine? Advanced users can build the precise engine with an older
@ -185,20 +192,20 @@ version of TensorFlow (v1.13) if desired and change use_precise to true
in mycroft.conf.
Y)es, I want to use the PocketSphinx engine or my own.
N)o, stop the installation."
if get_YN ; then
if [[ ! -f /etc/mycroft/mycroft.conf ]]; then
$SUDO mkdir -p /etc/mycroft
$SUDO touch /etc/mycroft/mycroft.conf
$SUDO bash -c 'echo "{ \"use_precise\": true }" > /etc/mycroft/mycroft.conf'
if get_YN ; then
if [[ ! -f /etc/mycroft/mycroft.conf ]]; then
$SUDO mkdir -p /etc/mycroft
$SUDO touch /etc/mycroft/mycroft.conf
$SUDO bash -c 'echo "{ \"use_precise\": false }" > /etc/mycroft/mycroft.conf'
else
# Ensure dependency installed to merge configs
disable_precise_later=true
fi
else
$SUDO bash -c 'jq ". + { \"use_precise\": true }" /etc/mycroft/mycroft.conf > tmp.mycroft.conf'
$SUDO mv -f tmp.mycroft.conf /etc/mycroft/mycroft.conf
echo -e "$HIGHLIGHT N - quit the installation $RESET"
exit 1
fi
else
echo -e "$HIGHLIGHT N - quit the installation $RESET"
exit 1
fi
echo
echo
fi
echo "
Do you want to run on 'master' or against a dev branch? Unless you are
@ -265,9 +272,11 @@ Would you like this to be added to your PATH in the .profile?'
if [[ ! -f ~/.profile_mycroft ]] ; then
# Only add the following to the .profile if .profile_mycroft
# doesn't exist, indicating this script has not been run before
echo '' >> ~/.profile
echo '# include Mycroft commands' >> ~/.profile
echo 'source ~/.profile_mycroft' >> ~/.profile
{
echo ''
echo '# include Mycroft commands'
echo 'source ~/.profile_mycroft'
} >> ~/.profile
fi
echo "
@ -289,9 +298,9 @@ fi" > ~/.profile_mycroft
echo 'This script will create that folder for you. This requires sudo'
echo 'permission and might ask you for a password...'
setup_user=$USER
setup_group=$(id -gn $USER)
setup_group=$(id -gn "$USER")
$SUDO mkdir -p /opt/mycroft/skills
$SUDO chown -R ${setup_user}:${setup_group} /opt/mycroft
$SUDO chown -R "${setup_user}":"${setup_group}" /opt/mycroft
echo 'Created!'
fi
if [[ ! -d skills ]] ; then
@ -319,7 +328,7 @@ If unsure answer yes.
fi
function os_is() {
[[ $(grep "^ID=" /etc/os-release | awk -F'=' '/^ID/ {print $2}' | sed 's/\"//g') == $1 ]]
[[ $(grep "^ID=" /etc/os-release | awk -F'=' '/^ID/ {print $2}' | sed 's/\"//g') == "$1" ]]
}
function os_is_like() {
@ -339,11 +348,11 @@ function redhat_common_install() {
}
function debian_install() {
APT_PACKAGE_LIST="git python3 python3-dev python3-setuptools libtool \
APT_PACKAGE_LIST=(git python3 python3-dev python3-setuptools libtool \
libffi-dev libssl-dev autoconf automake bison swig libglib2.0-dev \
portaudio19-dev mpg123 screen flac curl libicu-dev pkg-config \
libjpeg-dev libfann-dev build-essential jq pulseaudio \
pulseaudio-utils"
pulseaudio-utils)
if dpkg -V libjack-jackd2-0 > /dev/null 2>&1 && [[ -z ${CI} ]] ; then
echo "
@ -351,10 +360,10 @@ We have detected that your computer has the libjack-jackd2-0 package installed.
Mycroft requires a conflicting package, and will likely uninstall this package.
On some systems, this can cause other programs to be marked for removal.
Please review the following package changes carefully."
read -p "Press enter to continue"
$SUDO apt-get install $APT_PACKAGE_LIST
read -rp "Press enter to continue"
$SUDO apt-get install "${APT_PACKAGE_LIST[@]}"
else
$SUDO apt-get install -y $APT_PACKAGE_LIST
$SUDO apt-get install -y "${APT_PACKAGE_LIST[@]}"
fi
}
@ -371,7 +380,15 @@ function fedora_install() {
function arch_install() {
$SUDO pacman -S --needed --noconfirm git python python-pip python-setuptools python-virtualenv python-gobject libffi swig portaudio mpg123 screen flac curl icu libjpeg-turbo base-devel jq pulseaudio pulseaudio-alsa
pkgs=( git python python-pip python-setuptools python-virtualenv python-gobject libffi swig portaudio mpg123 screen flac curl icu libjpeg-turbo base-devel jq )
if ! pacman -Qs pipewire-pulse > /dev/null
then
pulse_pkgs=( pulseaudio pulseaudio-alsa )
pkgs=( "${pkgs[@]}" "${pulse_pkgs[@]}" )
fi
$SUDO pacman -S --needed --noconfirm "${pkgs[@]}"
pacman -Qs '^fann$' &> /dev/null || (
git clone https://aur.archlinux.org/fann.git
@ -398,11 +415,30 @@ function redhat_install() {
}
function gentoo_install() {
$SUDO emerge --noreplace dev-vcs/git dev-lang/python dev-python/setuptools dev-python/pygobject dev-python/requests sys-devel/libtool virtual/libffi virtual/jpeg dev-libs/openssl sys-devel/autoconf sys-devel/bison dev-lang/swig dev-libs/glib media-libs/portaudio media-sound/mpg123 media-libs/flac net-misc/curl sci-mathematics/fann sys-devel/gcc app-misc/jq media-libs/alsa-lib dev-libs/icu
$SUDO emerge --noreplace dev-vcs/git dev-lang/python dev-python/setuptools dev-python/pygobject dev-python/requests sys-devel/libtool dev-libs/libffi virtual/jpeg dev-libs/openssl sys-devel/autoconf sys-devel/bison dev-lang/swig dev-libs/glib media-libs/portaudio media-sound/mpg123 media-libs/flac net-misc/curl sci-mathematics/fann sys-devel/gcc app-misc/jq media-libs/alsa-lib dev-libs/icu
}
function alpine_install() {
$SUDO apk add --virtual makedeps-mycroft-core alpine-sdk git python3 py3-pip py3-setuptools py3-virtualenv mpg123 vorbis-tools pulseaudio-utils fann-dev automake autoconf libtool pcre2-dev pulseaudio-dev alsa-lib-dev swig python3-dev portaudio-dev libjpeg-turbo-dev
$SUDO apk add --virtual .makedeps-mycroft-core \
alpine-sdk \
alsa-lib-dev \
autoconf \
automake \
fann-dev \
git \
libjpeg-turbo-dev \
libtool \
mpg123 \
pcre2-dev \
portaudio-dev \
pulseaudio-utils \
py3-pip \
py3-setuptools \
py3-virtualenv \
python3 \
python3-dev \
swig \
vorbis-tools
}
function install_deps() {
@ -445,7 +481,7 @@ function install_deps() {
${YELLOW}Make sure to manually install:$BLUE git python3 python-setuptools python-venv pygobject libtool libffi libjpg openssl autoconf bison swig glib2.0 portaudio19 mpg123 flac curl fann g++ jq\n$RESET"
echo 'Warning: Failed to install all dependencies. Continue? y/N'
read -n1 continue
read -rn1 continue
if [[ $continue != 'y' ]] ; then
exit 1
fi
@ -457,16 +493,30 @@ VIRTUALENV_ROOT=${VIRTUALENV_ROOT:-"${TOP}/.venv"}
function install_venv() {
$opt_python -m venv "${VIRTUALENV_ROOT}/" --without-pip
# Check if old script for python 3.6 is needed
if "${VIRTUALENV_ROOT}/bin/${opt_python}" --version | grep " 3.6" > /dev/null; then
GET_PIP_URL="https://bootstrap.pypa.io/pip/3.6/get-pip.py"
else
GET_PIP_URL="https://bootstrap.pypa.io/get-pip.py"
fi
# Force version of pip for reproducability, but there is nothing special
# about this version. Update whenever a new version is released and
# verified functional.
curl https://bootstrap.pypa.io/get-pip.py | "${VIRTUALENV_ROOT}/bin/python" - 'pip==20.0.2'
curl "${GET_PIP_URL}" | "${VIRTUALENV_ROOT}/bin/${opt_python}" - 'pip==20.0.2'
# Function status depending on if pip exists
[[ -x ${VIRTUALENV_ROOT}/bin/pip ]]
}
install_deps
# It's later. Update existing config with jq.
if [[ $disable_precise_later == true ]]; then
$SUDO bash -c 'jq ". + { \"use_precise\": false }" /etc/mycroft/mycroft.conf > tmp.mycroft.conf'
$SUDO mv -f tmp.mycroft.conf /etc/mycroft/mycroft.conf
fi
# Configure to use the standard commit template for
# this repo only.
git config commit.template .gitmessage
@ -479,7 +529,7 @@ else
# first, look for a build of mimic in the folder
has_mimic=''
if [[ -f ${TOP}/mimic/bin/mimic ]] ; then
has_mimic=$(${TOP}/mimic/bin/mimic -lv | grep Voice) || true
has_mimic=$("${TOP}"/mimic/bin/mimic -lv | grep Voice) || true
fi
# in not, check the system path
@ -506,6 +556,7 @@ if [[ ! -x ${VIRTUALENV_ROOT}/bin/activate ]] ; then
fi
# Start the virtual environment
# shellcheck source=/dev/null
source "${VIRTUALENV_ROOT}/bin/activate"
cd "$TOP"
@ -514,7 +565,7 @@ HOOK_FILE='./.git/hooks/pre-commit'
if [[ -n $INSTALL_PRECOMMIT_HOOK ]] || grep -q 'MYCROFT DEV SETUP' $HOOK_FILE; then
if [[ ! -f $HOOK_FILE ]] || grep -q 'MYCROFT DEV SETUP' $HOOK_FILE; then
echo 'Installing PEP8 check as precommit-hook'
echo "#! $(which python)" > $HOOK_FILE
echo "#! $(command -v python)" > $HOOK_FILE
echo '# MYCROFT DEV SETUP' >> $HOOK_FILE
cat ./scripts/pre-commit >> $HOOK_FILE
chmod +x $HOOK_FILE
@ -532,17 +583,15 @@ if [[ ! -f $VENV_PATH_FILE ]] ; then
echo "import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)" >> "$VENV_PATH_FILE" || return 1
fi
if ! grep -q "$TOP" $VENV_PATH_FILE ; then
if ! grep -q "$TOP" "$VENV_PATH_FILE" ; then
echo 'Adding mycroft-core to virtualenv path'
sed -i.tmp '1 a\
'"$TOP"'
' "$VENV_PATH_FILE"
sed -i.tmp "1 a$TOP" "$VENV_PATH_FILE"
fi
# install required python modules
if ! pip install -r requirements/requirements.txt ; then
echo 'Warning: Failed to install required dependencies. Continue? y/N'
read -n1 continue
read -rn1 continue
if [[ $continue != 'y' ]] ; then
exit 1
fi
@ -553,7 +602,7 @@ if [[ ! $(pip install -r requirements/extra-audiobackend.txt) ||
! $(pip install -r requirements/extra-stt.txt) ||
! $(pip install -r requirements/extra-mark1.txt) ]] ; then
echo 'Warning: Failed to install some optional dependencies. Continue? y/N'
read -n1 continue
read -rn1 continue
if [[ $continue != 'y' ]] ; then
exit 1
fi
@ -565,7 +614,7 @@ if ! pip install -r requirements/tests.txt ; then
fi
SYSMEM=$(free | awk '/^Mem:/ { print $2 }')
MAXCORES=$(($SYSMEM / 2202010))
MAXCORES=$((SYSMEM / 2202010))
MINCORES=1
CORES=$(nproc)
@ -590,7 +639,7 @@ cd "$TOP"
if [[ $build_mimic == 'y' || $build_mimic == 'Y' ]] ; then
echo 'WARNING: The following can take a long time to run!'
"${TOP}/scripts/install-mimic.sh" " $CORES"
"${TOP}/scripts/install-mimic.sh" "$CORES"
else
echo 'Skipping mimic build.'
fi

View File

@ -74,13 +74,7 @@ def handle_speak(event):
# so we likely will want to get rid of this when not running on Mimic
if (config.get('enclosure', {}).get('platform') != "picroft" and
len(re.findall('<[^>]*>', utterance)) == 0):
# Remove any whitespace present after the period,
# if a character (only alpha) ends with a period
# ex: A. Lincoln -> A.Lincoln
# so that we don't split at the period
utterance = re.sub(r'\b([A-za-z][\.])(\s+)', r'\g<1>', utterance)
chunks = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\;|\?)\s',
utterance)
chunks = tts.preprocess_utterance(utterance)
# Apply the listen flag to the last chunk, set the rest to False
chunks = [(chunks[i], listen if i == len(chunks) - 1 else False)
for i in range(len(chunks))]
@ -116,10 +110,9 @@ def mute_and_speak(utterance, ident, listen=False):
# update TTS object if configuration has changed
if tts_hash != hash(str(config.get('tts', ''))):
global tts
# Stop tts playback thread
tts.playback.stop()
tts.playback.join()
# Create new tts instance
if tts:
tts.playback.detach_tts(tts)
tts = TTSFactory.create()
tts.init(bus)
tts_hash = hash(str(config.get('tts', '')))

View File

@ -264,7 +264,7 @@ class Enclosure:
# Load any already stored Data
data = self.datastore.get(namespace, {})
for key in data:
for key in dict(data):
msg = {"type": "mycroft.session.set",
"namespace": namespace,
"data": {key: data[key]}}

View File

@ -15,6 +15,7 @@
import subprocess
import time
from alsaaudio import Mixer
from os.path import join
from threading import Thread, Timer
import serial
@ -165,10 +166,11 @@ class EnclosureReader(Thread):
if "unit.factory-reset" in data:
self.bus.emit(Message("speak", {
'utterance': mycroft.dialog.get("reset to factory defaults")}))
subprocess.call(
(f'rm {xdg.BaseDirectory.save_config_path("mycroft")}'
'/mycroft/identity/identity2.json'),
shell=True)
xdg_identity_path = join(xdg.BaseDirectory.xdg_config_home,
'mycroft',
'identity',
'identity2.json')
subprocess.call(f'rm {xdg_identity_path}', shell=True)
subprocess.call(
'rm ~/.mycroft/identity/identity2.json',
shell=True)

View File

@ -77,7 +77,7 @@ class HotWordEngine:
self.key_phrase = str(key_phrase).lower()
if config is None:
config = Configuration.get().get("hot_words", {})
config = Configuration.get().get("hotwords", {})
config = config.get(self.key_phrase, {})
self.config = config
@ -201,7 +201,7 @@ class PreciseHotword(HotWordEngine):
# Make sure we pick the key we need from wherever it's located,
# but save to a writeable location only
local_conf = LocalConf(
join(xdg.BaseDirectory.save_config_path('mycroft'), 'mycroft.conf')
join(xdg.BaseDirectory.xdg_config_home, 'mycroft', 'mycroft.conf')
)
for conf_dir in xdg.BaseDirectory.load_config_paths('mycroft'):

View File

@ -185,7 +185,7 @@ def load_settings():
LOG.warning(" Note that this location is deprecated and will" +
" not be used in the future")
LOG.warning(" Please move it to " +
os.path.join(xdg.BaseDirectory.save_config_path('mycroft'),
os.path.join(xdg.BaseDirectory.xdg_config_home, 'mycroft',
filename))
config_file = path

View File

@ -14,19 +14,26 @@
# limitations under the License.
#
import inflection
import json
from os.path import exists, isfile, join
import os
import re
from os.path import exists, isfile, join, dirname
from requests import RequestException
import xdg.BaseDirectory
from requests import RequestException
from mycroft.util.combo_lock import ComboLock
from mycroft.util.file_utils import get_temp_path
from mycroft.util import camel_case_split
from mycroft.util.json_helper import load_commented_json, merge_dict
from mycroft.util.log import LOG
from .locations import DEFAULT_CONFIG, USER_CONFIG, OLD_USER_CONFIG
from .locations import SYSTEM_CONFIG
from .locations import (
DEFAULT_CONFIG,
OLD_USER_CONFIG,
SYSTEM_CONFIG,
USER_CONFIG
)
def is_remote_list(values):
@ -53,7 +60,8 @@ def translate_remote(config, setting):
if k not in IGNORED_SETTINGS:
# Translate the CamelCase values stored remotely into the
# Python-style names used within mycroft-core.
key = inflection.underscore(re.sub(r"Setting(s)?", "", k))
key = re.sub(r"Setting(s)?", "", k)
key = camel_case_split(key).replace(" ", "_").lower()
if isinstance(v, dict):
config[key] = config.get(key, {})
translate_remote(config[key], v)
@ -85,8 +93,11 @@ def translate_list(config, values):
class LocalConf(dict):
"""Config dictionary from file."""
_lock = ComboLock(get_temp_path('local-conf.lock'))
def __init__(self, path):
super(LocalConf, self).__init__()
self.is_valid = True # is loaded json valid, updated when load occurs
if path:
self.path = path
self.load_local(path)
@ -107,29 +118,54 @@ class LocalConf(dict):
except Exception as e:
LOG.error("Error loading configuration '{}'".format(path))
LOG.error(repr(e))
self.is_valid = False
else:
LOG.debug("Configuration '{}' not defined, skipping".format(path))
def store(self, path=None):
"""Cache the received settings locally.
def store(self, path=None, force=False):
"""Save config to disk.
The cache will be used if the remote is unreachable to load settings
that are as close to the user's as possible.
path (str): path to store file to, if missing will use the path from
where the config was loaded.
force (bool): Set to True if writing should occur despite the original
was malformed.
Returns:
(bool) True if save was successful, else False.
"""
path = path or self.path
with open(path, 'w') as f:
json.dump(self, f, indent=2)
result = False
with self._lock:
path = path or self.path
config_dir = dirname(path)
if not exists(config_dir):
os.makedirs(config_dir)
if self.is_valid or force:
with open(path, 'w') as f:
json.dump(self, f, indent=2)
result = True
else:
LOG.warning((f'"{path}" was not a valid config file when '
'loaded, will not save config. Please correct '
'the json or remove it to allow updates.'))
result = False
return result
def merge(self, conf):
merge_dict(self, conf)
class RemoteConf(LocalConf):
_lock = ComboLock(get_temp_path('remote-conf.lock'))
"""Config dictionary fetched from mycroft.ai."""
def __init__(self, cache=None):
super(RemoteConf, self).__init__(None)
cache = cache or join(xdg.BaseDirectory.save_cache_path('mycroft'),
cache = cache or join(xdg.BaseDirectory.xdg_cache_home, 'mycroft',
'web_cache.json')
from mycroft.api import is_paired
if not is_paired():
@ -158,7 +194,7 @@ class RemoteConf(LocalConf):
translate_remote(config, setting)
for key in config:
self.__setitem__(key, config[key])
self.store(cache)
self.store(cache, force=True)
except RequestException as e:
LOG.error("RequestException fetching remote configuration: {}"
@ -179,7 +215,7 @@ def _log_old_location_deprecation():
" Note that this location is deprecated and will"
" not be used in the future\n"
" Please move it to "
f"{xdg.BaseDirectory.save_config_path('mycroft')}")
f"{join(xdg.BaseDirectory.xdg_config_home, 'mycroft')}")
class Configuration:
@ -236,13 +272,13 @@ class Configuration:
_log_old_location_deprecation()
configs.append(LocalConf(OLD_USER_CONFIG))
# Then use the system config (/etc/mycroft/mycroft.conf)
configs.append(LocalConf(SYSTEM_CONFIG))
# Then use remote config
if remote:
configs.append(RemoteConf())
# Then use the system config (/etc/mycroft/mycroft.conf)
configs.append(LocalConf(SYSTEM_CONFIG))
# Then use the config that comes with the package
configs.append(LocalConf(DEFAULT_CONFIG))

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from os.path import join, dirname, expanduser, exists
from os.path import join, dirname, expanduser
import xdg.BaseDirectory
@ -23,24 +23,11 @@ SYSTEM_CONFIG = os.environ.get('MYCROFT_SYSTEM_CONFIG',
# Make sure we support the old location still
# Deprecated and will be removed eventually
OLD_USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf')
USER_CONFIG = join(xdg.BaseDirectory.save_config_path('mycroft'),
'mycroft.conf')
USER_CONFIG = join(xdg.BaseDirectory.xdg_config_home,
'mycroft',
'mycroft.conf'
)
REMOTE_CONFIG = "mycroft.ai"
WEB_CONFIG_CACHE = os.environ.get('MYCROFT_WEB_CACHE',
'/var/tmp/mycroft_web_cache.json')
def __ensure_folder_exists(path):
""" Make sure the directory for the specified path exists.
Args:
path (str): path to config file
"""
directory = dirname(path)
if not exists(directory):
os.makedirs(directory)
__ensure_folder_exists(WEB_CONFIG_CACHE)
__ensure_folder_exists(USER_CONFIG)

View File

@ -263,6 +263,14 @@
// If not defined, the default log level is INFO.
//"log_level": "INFO",
// Format of logs to store.
// NOTE: This configuration setting is special and can only be changed in the
// SYSTEM or USER configuration file, it will not be read if defined in the
// DEFAULT (here) or in the REMOTE mycroft config.
// If not defined, the default log format is:
// {asctime} | {levelname:8} | {process:5} | {name} | {message}
//"log_format": "{asctime} | {levelname:8} | {process:5} | {name} | {message}",
// Messagebus types that will NOT be output to logs
"ignore_logs": ["enclosure.mouth.viseme", "enclosure.mouth.display"],
@ -297,7 +305,7 @@
// Override: REMOTE
"tts": {
// Engine. Options: "mimic", "mimic2", "google", "marytts", "fatts", "espeak",
// "spdsay", "responsive_voice", "yandex", "polly", "mozilla"
// "spdsay", "yandex", "polly", "mozilla"
"pulse_duck": false,
"module": "mimic",
"polly": {

View File

@ -373,5 +373,5 @@ class SkillGUI:
Clear pages loaded through this interface and remove the skill
reference to make ref counting warning more precise.
"""
self.clear()
self.release()
self.skill = None

View File

@ -43,9 +43,14 @@ class IdentityManager:
def _load():
LOG.debug('Loading identity')
try:
with FileSystemAccess('identity').open('identity2.json', 'r') as f:
IdentityManager.__identity = DeviceIdentity(**json.load(f))
except Exception:
identity_dir = FileSystemAccess('identity')
if identity_dir.exists('identity2.json'):
with identity_dir.open('identity2.json', 'r') as f:
IdentityManager.__identity = DeviceIdentity(**json.load(f))
else:
IdentityManager.__identity = DeviceIdentity()
except Exception as e:
LOG.exception(f'Failed to load identity file: {repr(e)}')
IdentityManager.__identity = DeviceIdentity()
@staticmethod

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
Deyəsən, Mycroft serverlərinə qoşula bilmirəm. Zəhmət olmasa mənimlə danışmazdan əvvəl bir neçə dəqiqə gözləyin.

View File

@ -0,0 +1,3 @@
ləğv et
ötür
bürax

View File

@ -0,0 +1,2 @@
Yeniləmələr yoxlanılır
Bir an gözləyin, özümü yeniləyirəm.

View File

@ -0,0 +1 @@
gün

View File

@ -0,0 +1 @@
saat

View File

@ -0,0 +1,3 @@
Bağışlayın, mən bunu başa düşmədim
bir daha deyə bilərsinizmi?
lütfən tekrar söyləyə bilərsinizmi?

View File

@ -0,0 +1,2 @@
son seçim
sonuncu

View File

@ -0,0 +1 @@
< < < YÜKLƏNİR < < <

View File

@ -0,0 +1 @@
YENİDƏN İŞƏ SALINIR...

View File

@ -0,0 +1 @@
< < < SYNC < < <

View File

@ -0,0 +1 @@
< < < YENİLƏNİR < < <

View File

@ -0,0 +1 @@
dəqiqə

View File

@ -0,0 +1 @@
Salam, mən Mycroft, sizin yeni yardımcınızam. Sizə yardım etmək üçün mən internetə qoşulmalıyam. Siz ya məni şəbəkə kabeli ilə qoşa, ya da Wi-Fi istifadə edə bilərsiniz. WiFi qurmaq üçün bu təlimatları yerinə yetirin:

View File

@ -0,0 +1,3 @@
yox
yo
mənfi

View File

@ -0,0 +1,4 @@
Deyəsən, mən İnternetə qoşulmamışam, lütfən, şəbəkə bağlantınızı yoxlayın.
Mən internetə bağlı deyiləm, lütfən, şəbəkə bağlantınızı yoxlayın.
indi internetə qoşula bilmirəm, lütfən, şəbəkə bağlantınızı yoxlayı
İnternetə qoşula bilmirəm, şəbəkə bağlantınızı yoxlayın.

View File

@ -0,0 +1 @@
Yükləməni bitirənə kimi bir az gözləyin.

View File

@ -0,0 +1 @@
ya

View File

@ -0,0 +1,3 @@
ai: S.İ.
mycroftai: maykraft S.İ.
spotify: spatifay

View File

@ -0,0 +1 @@
Mən fabrik tənzimlərimə qayıtmışam.

View File

@ -0,0 +1 @@
saniyə

View File

@ -0,0 +1 @@
{{skill}} Bacarıqlar işlərkən xəta baş verdi

View File

@ -0,0 +1 @@
Bacarıqlar Yeniləndi. Mən sizə yardım etməyə hazıram.

View File

@ -0,0 +1 @@
bacarıqları yeniləyərkən xəta baş verdi

View File

@ -0,0 +1 @@
Saatımı internetlə sinxronlaşdırdıqdan sonra yenidən başlamalıyam, birazajan qayıdacağam.

View File

@ -0,0 +1,4 @@
bəli
evet
hən
lütfən

View File

@ -0,0 +1,30 @@
where
what's
which
them
they
when
what
that
will
from
that
also
who
how
did
and
but
the
too
why
for
is
it
do
or
to
of
a

View File

@ -3,3 +3,5 @@ ai: A.I.
mycroftai: mycroft A.I.
spotify: spot-ify
corgi: core-gee
ip: eye pea
wikipedia: wickee-peedia

View File

@ -0,0 +1 @@
и

View File

@ -0,0 +1,4 @@
Испытываю затруднения с поключением к серверам Mycroft. Подождите несколько минут, а потом можете спросить меня снова.
Испытываю затруднения с поключением к серверам Mycroft. Пожалуйста, дайте мне пару минут, а потом можете спросить снова.
Похоже я не могу подключиться к серверам Mycroft. Подождите несколько минут, а потом можете спросить меня снова.
Похоже я не могу подключиться к серверам Mycroft. Пожалуйста, дайте мне пару минут, а потом можете спросить снова.

View File

@ -0,0 +1,3 @@
отмена
не важно
ну и ладно

View File

@ -0,0 +1,2 @@
Проверяю обновления
Подождите минутку, я всё ещё обновляюсь

View File

@ -1 +1 @@
дни
дней

View File

@ -1 +1 @@
часы
часов

View File

@ -0,0 +1,5 @@
Извините, мне не понятно
Боюсь, мне не удалось вас понять
Не могли бы вы повторить?
Не могли бы повторить снова?
Повторите ещё раз, пожалуйста

View File

@ -0,0 +1,4 @@
последний
последний вариант
последний выбор
самый последний

View File

@ -0,0 +1 @@
Данные о взаимодействии больше не будут отправляться в Mycroft AI

View File

@ -0,0 +1 @@
Теперь я загружу данные взаимодействия в Mycroft AI, чтобы стать умнее. В настоящее время это включает записи активации слов для пробуждения

View File

@ -0,0 +1 @@
< < < ЗАГРУЗКА < < <

View File

@ -0,0 +1 @@
ПЕРЕЗАГРУЗКА...

View File

@ -0,0 +1 @@
< < < СИХРОНИЗАЦИЯ < < <

View File

@ -0,0 +1 @@
< < < ОБНОВЛЕНИЕ < < <

View File

@ -1 +1 @@
минуты
минут

View File

@ -0,0 +1 @@
Привет, я Майкрофт, ваш новый ассистент. Для помощи вам мне нужно подключиться к интернету. Вы можете подключить меня к сетевому кабелю или использовать Wi-Fi. Следуйте этим инструкциям для подключения Wi-Fi:

View File

@ -1,4 +1,4 @@
нет
неа
не-а
нет
отрицательно
отказ

View File

@ -0,0 +1,5 @@
Кажется, у меня нет подключения к интернету. Проверьте соединение, пожалуйста
Похоже, у меня нет подключения к интернету. Проверьте соединение, пожалуйста
Не могу подключиться к интернету. Проверьте соединение, пожалуйста
Не могу попасть в интернет. Проверьте соединение, пожалуйста
У меня проблема с подключением к интернету. Проверьте соединение, пожалуйста

View File

@ -0,0 +1 @@
Подождите минутку, пока я закончу загружаться

View File

@ -0,0 +1 @@
или

View File

@ -0,0 +1,19 @@
ai: эй-ай
duckduckgo: дак-дак-гоу
fairytalez: фэйри-тэйлс
homeassistant: хоум-ассистант
iot: ай-оу-ти
lifx: лайф-икс
mycroft: май-крафт
mycroftai: май-крафт эй-ай
myepisodes: май-эпизодс
spotify: споти-фай
ssh: эс-эс-эйч
wifi: вай-фай
wi-fi: вай-фай
wikiquote: вики-квоут
майкрофт: май-крафт
майкрофта: май-крафта
майкрофте: май-крафте
майкрофтом: май-крафтом
майкрофту: май-крафту

View File

@ -0,0 +1 @@
Меня сбросили до заводских настроек

View File

@ -1 +1 @@
секунду
секунда

View File

@ -1 +1 @@
секунды
секунд

View File

@ -0,0 +1 @@
Во время обработки запроса в {{skill}} прозошла ошибка

View File

@ -0,0 +1,2 @@
Теперь я в актуальном состоянии
Умения обновлены. Хотите я помогу вам?

View File

@ -0,0 +1 @@
При обновлении тем оформления произошла ошибка

View File

@ -0,0 +1 @@
Логин по SSH был отключён

View File

@ -0,0 +1 @@
Логин по SSH теперь разрешён

View File

@ -0,0 +1 @@
Мне нужно перезагрузиться после синхронизации часов с интернетом, скоро вернусь

View File

@ -1,4 +1,3 @@
да
ага
да
конечно

View File

@ -0,0 +1,29 @@
var
vad
vilken
dom
dem
när
där
som
kommer
från
också
vem
hur
gjorde
gör
kommer
och
men
också
varför
för
är
det
den
eller
till
av
en
ett

View File

@ -5,37 +5,40 @@ import org.kde.kirigami 2.4 as Kirigami
import Mycroft 1.0 as Mycroft
Mycroft.Delegate {
Mycroft.CardDelegate {
id: systemTextFrame
skillBackgroundColorOverlay: "#000000"
cardBackgroundOverlayColor: "#000000"
property bool hasTitle: sessionData.title.length > 0 ? true : false
Component.onCompleted: {
console.log(hasTitle)
}
contentItem: Rectangle {
color: "transparent"
contentItem: ColumnLayout {
Label {
id: systemTextFrameTitle
Layout.fillWidth: true
font.pixelSize: Math.min(systemTextFrame.height/4, Math.max(systemTextFrame.height/10, systemTextFrameMainBody.fontInfo.pixelSize * 1.4))
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
visible: hasTitle
enabled: hasTitle
font.family: "Noto Sans"
font.weight: Font.Bold
text: sessionData.title
}
Mycroft.AutoFitLabel {
id: systemTextFrameMainBody
Layout.fillWidth: true
Layout.fillHeight: true
wrapMode: Text.Wrap
font.family: "Noto Sans"
font.weight: Font.Bold
text: sessionData.text
ColumnLayout {
anchors.fill: parent
Mycroft.AutoFitLabel {
id: systemTextFrameTitle
wrapMode: Text.Wrap
visible: hasTitle
enabled: hasTitle
Layout.fillWidth: true
Layout.fillHeight: true
font.family: "Noto Sans"
font.weight: Font.Bold
text: sessionData.title
}
Mycroft.AutoFitLabel {
id: systemTextFrameMainBody
wrapMode: Text.Wrap
font.family: "Noto Sans"
Layout.fillWidth: true
Layout.fillHeight: true
font.weight: Font.Bold
text: sessionData.text
}
}
}
}

View File

@ -11,11 +11,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import IntEnum
from abc import ABC, abstractmethod
from .mycroft_skill import MycroftSkill
from mycroft.util.file_utils import resolve_resource_file
class CQSMatchLevel(IntEnum):
EXACT = 1 # Skill could find a specific answer for the question
@ -32,11 +33,19 @@ def is_CQSVisualMatchLevel(match_level):
return isinstance(match_level, type(CQSVisualMatchLevel.EXACT))
VISUAL_DEVICES = ['mycroft_mark_2']
"""these are for the confidence calculation"""
# how much each topic word is worth
# when found in the answer
TOPIC_MATCH_RELEVANCE = 5
# elevate relevance above all else
RELEVANCE_MULTIPLIER = 2
def handles_visuals(platform):
return platform in VISUAL_DEVICES
# we like longer articles but only so much
MAX_ANSWER_LEN_FOR_CONFIDENCE = 50
# higher number - less bias for word length
WORD_COUNT_DIVISOR = 100
class CommonQuerySkill(MycroftSkill, ABC):
@ -49,8 +58,29 @@ class CommonQuerySkill(MycroftSkill, ABC):
This class works in conjunction with skill-query which collects
answers from several skills presenting the best one available.
"""
def __init__(self, name=None, bus=None):
super().__init__(name, bus)
noise_words_filepath = "text/%s/noise_words.list" % (self.lang,)
noise_words_filename = resolve_resource_file(noise_words_filepath)
self.translated_noise_words = []
try:
if noise_words_filename:
with open(noise_words_filename) as f:
read_noise_words = f.read().strip()
self.translated_noise_words = read_noise_words.split()
else:
raise FileNotFoundError
except FileNotFoundError:
self.log.warning("Missing noise_words.list file in "
f"res/text/{self.lang}")
# these should probably be configurable
self.level_confidence = {
CQSMatchLevel.EXACT: 0.9,
CQSMatchLevel.CATEGORY: 0.6,
CQSMatchLevel.GENERAL: 0.5
}
def bind(self, bus):
"""Overrides the default bind method of MycroftSkill.
@ -80,7 +110,8 @@ class CommonQuerySkill(MycroftSkill, ABC):
level = result[1]
answer = result[2]
callback = result[3] if len(result) > 3 else None
confidence = self.__calc_confidence(match, search_phrase, level)
confidence = self.__calc_confidence(
match, search_phrase, level, answer)
self.bus.emit(message.response({"phrase": search_phrase,
"skill_id": self.skill_id,
"answer": answer,
@ -92,27 +123,57 @@ class CommonQuerySkill(MycroftSkill, ABC):
"skill_id": self.skill_id,
"searching": False}))
def __calc_confidence(self, match, phrase, level):
def remove_noise(self, phrase):
"""remove noise to produce essence of question"""
phrase = ' ' + phrase + ' '
for word in self.translated_noise_words:
mtch = ' ' + word + ' '
if phrase.find(mtch) > -1:
phrase = phrase.replace(mtch, " ")
phrase = ' '.join(phrase.split())
return phrase.strip()
def __calc_confidence(self, match, phrase, level, answer):
# Assume the more of the words that get consumed, the better the match
consumed_pct = len(match.split()) / len(phrase.split())
if consumed_pct > 1.0:
consumed_pct = 1.0
consumed_pct /= 10
# bonus for more sentences
num_sentences = float(float(len(answer.split("."))) / float(10))
# Add bonus if match has visuals and the device supports them.
platform = self.config_core.get('enclosure', {}).get('platform')
if is_CQSVisualMatchLevel(level) and handles_visuals(platform):
bonus = 0.0
if is_CQSVisualMatchLevel(level) and self.gui.connected:
bonus = 0.1
else:
bonus = 0
if int(level) == int(CQSMatchLevel.EXACT):
return 0.9 + (consumed_pct / 10) + bonus
elif int(level) == int(CQSMatchLevel.CATEGORY):
return 0.6 + (consumed_pct / 10) + bonus
elif int(level) == int(CQSMatchLevel.GENERAL):
return 0.5 + (consumed_pct / 10) + bonus
else:
return 0.0 # should never happen
# extract topic
topic = self.remove_noise(match)
# calculate relevance
answer = answer.lower()
matches = 0
for word in topic.split(' '):
if answer.find(word) > -1:
matches += TOPIC_MATCH_RELEVANCE
answer_size = len(answer.split(" "))
answer_size = min(MAX_ANSWER_LEN_FOR_CONFIDENCE, answer_size)
relevance = 0.0
if answer_size > 0:
relevance = float(float(matches) / float(answer_size))
relevance = relevance * RELEVANCE_MULTIPLIER
# extra credit for more words up to a point
wc_mod = float(float(answer_size) / float(WORD_COUNT_DIVISOR)) * 2
confidence = self.level_confidence[level] + \
consumed_pct + bonus + num_sentences + relevance + wc_mod
return confidence
def __handle_query_action(self, message):
"""Message handler for question:action.

View File

@ -21,7 +21,10 @@ from mycroft.util.log import LOG
from mycroft.util.parse import normalize
from mycroft.metrics import report_timing, Stopwatch
from .intent_services import (
AdaptService, AdaptIntent, FallbackService, PadatiousService, IntentMatch
AdaptService, AdaptIntent,
FallbackService,
PadatiousService, PadatiousMatcher,
IntentMatch
)
from .intent_service_interface import open_intent_envelope
@ -280,13 +283,16 @@ class IntentService:
stopwatch = Stopwatch()
# Create matchers
padatious_matcher = PadatiousMatcher(self.padatious_service)
# List of functions to use to match the utterance with intent.
# These are listed in priority order.
match_funcs = [
self._converse, self.padatious_service.match_high,
self._converse, padatious_matcher.match_high,
self.adapt_service.match_intent, self.fallback.high_prio,
self.padatious_service.match_medium, self.fallback.medium_prio,
self.padatious_service.match_low, self.fallback.low_prio
padatious_matcher.match_medium, self.fallback.medium_prio,
padatious_matcher.match_low, self.fallback.low_prio
]
match = None
@ -305,6 +311,10 @@ class IntentService:
# Launch skill if not handled by the match function
if match.intent_type:
reply = message.reply(match.intent_type, match.intent_data)
# Add back original list of utterances for intent handlers
# match.intent_data only includes the utterance with the
# highest confidence.
reply.data["utterances"] = utterances
self.bus.emit(reply)
else:
@ -354,12 +364,18 @@ class IntentService:
Args:
message (Message): message containing vocab info
"""
start_concept = message.data.get('start')
end_concept = message.data.get('end')
# TODO: 22.02 Remove backwards compatibility
if _is_old_style_keyword_message(message):
LOG.warning('Deprecated: Registering keywords with old message. '
'This will be removed in v22.02.')
_update_keyword_message(message)
entity_value = message.data.get('entity_value')
entity_type = message.data.get('entity_type')
regex_str = message.data.get('regex')
alias_of = message.data.get('alias_of')
self.adapt_service.register_vocab(start_concept, end_concept,
alias_of, regex_str)
self.adapt_service.register_vocabulary(entity_value, entity_type,
alias_of, regex_str)
self.registered_vocab.append(message.data)
def handle_register_intent(self, message):
@ -434,17 +450,20 @@ class IntentService:
lang = message.data.get("lang", "en-us")
combined = _normalize_all_utterances([utterance])
# Create matchers
padatious_matcher = PadatiousMatcher(self.padatious_service)
# List of functions to use to match the utterance with intent.
# These are listed in priority order.
# TODO once we have a mechanism for checking if a fallback will
# trigger without actually triggering it, those should be added here
match_funcs = [
self.padatious_service.match_high,
padatious_matcher.match_high,
self.adapt_service.match_intent,
# self.fallback.high_prio,
self.padatious_service.match_medium,
padatious_matcher.match_medium,
# self.fallback.medium_prio,
self.padatious_service.match_low,
padatious_matcher.match_low,
# self.fallback.low_prio
]
# Loop through the matching functions until a match is found.
@ -550,3 +569,29 @@ class IntentService:
self.bus.emit(message.reply(
"intent.service.padatious.entities.manifest",
{"entities": self.padatious_service.registered_entities}))
def _is_old_style_keyword_message(message):
"""Simple check that the message is not using the updated format.
TODO: Remove in v22.02
Args:
message (Message): Message object to check
Returns:
(bool) True if this is an old messagem, else False
"""
return ('entity_value' not in message.data and 'start' in message.data)
def _update_keyword_message(message):
"""Make old style keyword registration message compatible.
Copies old keys in message data to new names.
Args:
message (Message): Message to update
"""
message.data['entity_value'] = message.data['start']
message.data['entity_type'] = message.data['end']

View File

@ -44,15 +44,27 @@ class IntentServiceInterface:
vocab_type(str): Keyword reference
entity (str): Primary keyword
aliases (list): List of alternative kewords
aliases (list): List of alternative keywords
"""
# TODO 22.02: Remove compatibility data
aliases = aliases or []
self.bus.emit(Message("register_vocab",
{'start': entity, 'end': vocab_type}))
entity_data = {'entity_value': entity, 'entity_type': vocab_type}
compatibility_data = {'start': entity, 'end': vocab_type}
self.bus.emit(
Message("register_vocab",
{**entity_data, **compatibility_data})
)
for alias in aliases:
self.bus.emit(Message("register_vocab", {
'start': alias, 'end': vocab_type, 'alias_of': entity
}))
alias_data = {
'entity_value': alias,
'entity_type': vocab_type,
'alias_of': entity}
compatibility_data = {'start': alias, 'end': vocab_type}
self.bus.emit(
Message("register_vocab",
{**alias_data, **compatibility_data})
)
def register_adapt_regex(self, regex):
"""Register a regex with the intent service.

View File

@ -1,4 +1,4 @@
from .adapt_service import AdaptService, AdaptIntent
from .base import IntentMatch
from .fallback_service import FallbackService
from .padatious_service import PadatiousService
from .padatious_service import PadatiousService, PadatiousMatcher

View File

@ -249,14 +249,35 @@ class AdaptService:
ret = None
return ret
# TODO 22.02: Remove this deprecated method
def register_vocab(self, start_concept, end_concept, alias_of, regex_str):
"""Register vocabulary."""
"""Register Vocabulary. DEPRECATED
This method should not be used, it has been replaced by
register_vocabulary().
"""
self.register_vocabulary(start_concept, end_concept,
alias_of, regex_str)
def register_vocabulary(self, entity_value, entity_type,
alias_of, regex_str):
"""Register skill vocabulary as adapt entity.
This will handle both regex registration and registration of normal
keywords. if the "regex_str" argument is set all other arguments will
be ignored.
Argument:
entity_value: the natural langauge word
entity_type: the type/tag of an entity instance
alias_of: entity this is an alternative for
"""
with self.lock:
if regex_str:
self.engine.register_regex_entity(regex_str)
else:
self.engine.register_entity(
start_concept, end_concept, alias_of=alias_of)
entity_value, entity_type, alias_of=alias_of)
def register_intent(self, intent):
"""Register new intent with adapt engine.

View File

@ -26,6 +26,76 @@ from mycroft.util.log import LOG
from .base import IntentMatch
class PadatiousMatcher:
"""Matcher class to avoid redundancy in padatious intent matching."""
def __init__(self, service):
self.service = service
self.has_result = False
self.ret = None
self.conf = None
def _match_level(self, utterances, limit):
"""Match intent and make sure a certain level of confidence is reached.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
limit (float): required confidence level.
"""
if not self.has_result:
padatious_intent = None
LOG.debug('Padatious Matching confidence > {}'.format(limit))
for utt in utterances:
for variant in utt:
intent = self.service.calc_intent(variant)
if intent:
best = padatious_intent.conf \
if padatious_intent else 0.0
if best < intent.conf:
padatious_intent = intent
padatious_intent.matches['utterance'] = utt[0]
if padatious_intent:
skill_id = padatious_intent.name.split(':')[0]
self.ret = IntentMatch(
'Padatious', padatious_intent.name,
padatious_intent.matches, skill_id
)
self.conf = padatious_intent.conf
self.has_result = True
if self.conf and self.conf > limit:
return self.ret
return None
def match_high(self, utterances, _=None, __=None):
"""Intent matcher for high confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
return self._match_level(utterances, 0.95)
def match_medium(self, utterances, _=None, __=None):
"""Intent matcher for medium confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
return self._match_level(utterances, 0.8)
def match_low(self, utterances, _=None, __=None):
"""Intent matcher for low confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
return self._match_level(utterances, 0.5)
class PadatiousService:
"""Service class for padatious intent matching."""
def __init__(self, bus, config):
@ -167,63 +237,6 @@ class PadatiousService:
self.registered_entities.append(message.data)
self._register_object(message, 'entity', self.container.load_entity)
def _match_level(self, utterances, limit):
"""Match intent and make sure a certain level of confidence is reached.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
limit (float): required confidence level.
"""
padatious_intent = None
LOG.debug('Padatious Matching confidence > {}'.format(limit))
for utt in utterances:
for variant in utt:
intent = self.calc_intent(variant)
if intent:
best = padatious_intent.conf if padatious_intent else 0.0
if best < intent.conf:
padatious_intent = intent
padatious_intent.matches['utterance'] = utt[0]
if padatious_intent and padatious_intent.conf > limit:
skill_id = padatious_intent.name.split(':')[0]
ret = IntentMatch(
'Padatious', padatious_intent.name, padatious_intent.matches,
skill_id
)
else:
ret = None
return ret
def match_high(self, utterances, _=None, __=None):
"""Intent matcher for high confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
return self._match_level(utterances, 0.95)
def match_medium(self, utterances, _=None, __=None):
"""Intent matcher for medium confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
return self._match_level(utterances, 0.8)
def match_low(self, utterances, _=None, __=None):
"""Intent matcher for low confidence.
Args:
utterances (list of tuples): Utterances to parse, originals paired
with optional normalized version.
"""
return self._match_level(utterances, 0.5)
@lru_cache(maxsize=2) # 2 catches both raw and normalized utts in cache
def calc_intent(self, utt):
"""Cached version of container calc_intent.

View File

@ -1168,9 +1168,8 @@ class MycroftSkill:
entity: word to register
entity_type: Intent handler entity to tie the word to
"""
self.bus.emit(Message('register_vocab', {
'start': entity, 'end': to_alnum(self.skill_id) + entity_type
}))
keyword_type = to_alnum(self.skill_id) + entity_type
self.intent_service.register_adapt_keyword(keyword_type, entity)
def register_regex(self, regex_str):
"""Register a new regex.

View File

@ -41,7 +41,7 @@ def remove_submodule_refs(module_name):
module_name: name of skill module.
"""
submodules = []
LOG.debug('Skill module'.format(module_name))
LOG.debug('Skill module: {}'.format(module_name))
# Collect found submodules
for m in sys.modules:
if m.startswith(module_name + '.'):

View File

@ -37,6 +37,18 @@ class STT(metaclass=ABCMeta):
self.recognizer = Recognizer()
self.can_stream = False
@property
def available_languages(self) -> set:
"""Return languages supported by this STT implementation in this state
This property should be overridden by the derived class to advertise
what languages that engine supports.
Returns:
set: supported languages
"""
return set()
@staticmethod
def init_language(config_core):
"""Helper method to get language code from Mycroft config."""

View File

@ -32,8 +32,32 @@ class ESpeak(TTS):
Returns:
tuple ((str) file location, None)
"""
subprocess.call(['espeak', '-v', self.lang + '+' + self.voice,
'-w', wav_file, sentence])
# Create Argument String for Espeak
arguments = ['espeak', '-v', self.lang + '+' + self.voice]
amplitude = self.config.get('amplitude')
if amplitude:
arguments.append('-a '+amplitude)
gap = self.config.get('gap')
if gap:
arguments.append('-g '+gap)
capital = self.config.get('capital')
if capital:
arguments.append('-k '+capital)
pitch = self.config.get('pitch')
if pitch:
arguments.append('-p '+pitch)
speed = self.config.get('speed')
if speed:
arguments.append('-s '+speed)
arguments.extend(['-w', wav_file, sentence])
subprocess.call(arguments)
return wav_file, None

View File

@ -70,6 +70,13 @@ class GoogleTTS(TTS):
self._google_lang = None
super(GoogleTTS, self).__init__(lang, config, GoogleTTSValidator(
self), 'mp3')
LOG.warning(
"The Google TTS module uses the gTTS Python package which itself "
"interfaces with the Google Translate text-to-speech API. This is "
"not intended for commercial or production usage. The service "
"may break at any time, and you are subject to their Terms of "
"Service that can be found at https://policies.google.com/terms"
)
@property
def google_lang(self):

View File

@ -130,8 +130,6 @@ class Mimic(TTS):
)
self.default_binary = get_mimic_binary()
self.clear_cache()
# Download subscriber voices if needed
self.subscriber_voices = get_subscriber_voices()
self.is_subscriber = DeviceApi().is_subscriber

View File

@ -39,22 +39,43 @@ from mycroft.util.plugins import load_plugin
from queue import Queue, Empty
from .cache import hash_sentence, TextToSpeechCache
_TTS_ENV = deepcopy(os.environ)
_TTS_ENV['PULSE_PROP'] = 'media.role=phone'
EMPTY_PLAYBACK_QUEUE_TUPLE = (None, None, None, None, None)
SSML_TAGS = re.compile(r'<[^>]*>')
WHITESPACE_AFTER_PERIOD = re.compile(r'\b([A-za-z][\.])(\s+)')
SENTENCE_DELIMITERS = re.compile(
r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\;|\?)\s'
)
def default_preprocess_utterance(utterance):
"""Default method for preprocessing Mycroft utterances for TTS.
Args:
utteance (str): Input utterance
Returns:
[str]: list of preprocessed sentences
"""
utterance = WHITESPACE_AFTER_PERIOD.sub(r'\g<1>', utterance)
chunks = SENTENCE_DELIMITERS.split(utterance)
return chunks
class PlaybackThread(Thread):
"""Thread class for playing back tts audio and sending
viseme data to enclosure.
"""
def __init__(self, queue):
super(PlaybackThread, self).__init__()
self.queue = queue
self.tts = []
self.bus = None
self._terminated = False
self._processing_queue = False
self.enclosure = None
@ -66,7 +87,28 @@ class PlaybackThread(Thread):
self.pulse_env = None
def init(self, tts):
self.tts = tts
"""DEPRECATED! Init the TTS Playback thread.
TODO: 22.02 Remove this
"""
self.attach_tts(tts)
self.set_bus(tts.bus)
def set_bus(self, bus):
"""Provide bus instance to the TTS Playback thread.
Args:
bus (MycroftBusClient): bus client
"""
self.bus = bus
def attach_tts(self, tts):
"""Add TTS to be cache checked."""
self.tts.append(tts)
def detach_tts(self, tts):
"""Remove TTS from cache check."""
self.tts.remove(tts)
def clear_queue(self):
"""Remove all pending playbacks."""
@ -90,7 +132,7 @@ class PlaybackThread(Thread):
the loop then wait for the playback process to finish before starting
checking the next position in queue.
If the queue is empty the tts.end_audio() is called possibly triggering
If the queue is empty the end_audio() is called possibly triggering
listening.
"""
while not self._terminated:
@ -100,7 +142,7 @@ class PlaybackThread(Thread):
self.blink(0.5)
if not self._processing_queue:
self._processing_queue = True
self.tts.begin_audio()
self.begin_audio()
stopwatch = Stopwatch()
with stopwatch:
@ -116,7 +158,7 @@ class PlaybackThread(Thread):
report_timing(ident, 'speech_playback', stopwatch)
if self.queue.empty():
self.tts.end_audio(listen)
self.end_audio(listen)
self._processing_queue = False
self.blink(0.2)
except Empty:
@ -124,9 +166,42 @@ class PlaybackThread(Thread):
except Exception as e:
LOG.exception(e)
if self._processing_queue:
self.tts.end_audio(listen)
self.end_audio(listen)
self._processing_queue = False
def begin_audio(self):
"""Perform befining of speech actions."""
# Create signals informing start of speech
if self.bus:
self.bus.emit(Message("recognizer_loop:audio_output_start"))
else:
LOG.warning("Speech started before bus was attached.")
def end_audio(self, listen):
"""Perform end of speech output actions.
Will inform the system that speech has ended and trigger the TTS's
cache checks. Listening will be triggered if requested.
Args:
listen (bool): True if listening event should be emitted
"""
if self.bus:
# Send end of speech signals to the system
self.bus.emit(Message("recognizer_loop:audio_output_end"))
if listen:
self.bus.emit(Message('mycroft.mic.listen'))
# Clear cache for all attached tts objects
# This is basically the only safe time
for tts in self.tts:
tts.cache.curate()
# This check will clear the filesystem IPC "signal"
check_for_signal("isSpeaking")
else:
LOG.warning("Speech started before bus was attached.")
def show_visemes(self, pairs):
"""Send viseme data to enclosure
@ -167,6 +242,8 @@ class TTS(metaclass=ABCMeta):
phonetic_spelling (bool): Whether to spell certain words phonetically
ssml_tags (list): Supported ssml properties. Ex. ['speak', 'prosody']
"""
queue = None
playback = None
def __init__(self, lang, config, validator, audio_ext='wav',
phonetic_spelling=True, ssml_tags=None):
@ -183,9 +260,12 @@ class TTS(metaclass=ABCMeta):
self.filename = get_temp_path('tts.wav')
self.enclosure = None
random.seed()
self.queue = Queue()
self.playback = PlaybackThread(self.queue)
self.playback.start()
if TTS.queue is None:
TTS.queue = Queue()
TTS.playback = PlaybackThread(TTS.queue)
TTS.playback.start()
self.spellings = self.load_spellings()
self.tts_name = type(self).__name__
self.cache = TextToSpeechCache(
@ -193,6 +273,18 @@ class TTS(metaclass=ABCMeta):
)
self.cache.clear()
@property
def available_languages(self) -> set:
"""Return languages supported by this TTS implementation in this state
This property should be overridden by the derived class to advertise
what languages that engine supports.
Returns:
set: supported languages
"""
return set()
def load_spellings(self):
"""Load phonetic spellings of words as dictionary."""
path = join('text', self.lang.lower(), 'phonetic_spellings.txt')
@ -240,9 +332,10 @@ class TTS(metaclass=ABCMeta):
bus: Mycroft messagebus connection
"""
self.bus = bus
self.playback.init(self)
TTS.playback.set_bus(bus)
TTS.playback.attach_tts(self)
self.enclosure = EnclosureAPI(self.bus)
self.playback.enclosure = self.enclosure
TTS.playback.enclosure = self.enclosure
def get_tts(self, sentence, wav_file):
"""Abstract method that a tts implementation needs to implement.
@ -294,7 +387,7 @@ class TTS(metaclass=ABCMeta):
return self.remove_ssml(utterance)
# find ssml tags in string
tags = re.findall('<[^>]*>', utterance)
tags = SSML_TAGS.findall(utterance)
for tag in tags:
if any(supported in tag for supported in self.ssml_tags):
@ -306,6 +399,21 @@ class TTS(metaclass=ABCMeta):
# return text with supported ssml tags only
return utterance.replace(" ", " ")
def preprocess_utterance(self, utterance):
"""Preprocess utterance into list of chunks suitable for the TTS.
Perform general chunking and TTS specific chunking.
"""
# Remove any whitespace present after the period,
# if a character (only alpha) ends with a period
# ex: A. Lincoln -> A.Lincoln
# so that we don't split at the period
chunks = default_preprocess_utterance(utterance)
result = []
for chunk in chunks:
result += self._preprocess_sentence(chunk)
return result
def _preprocess_sentence(self, sentence):
"""Default preprocessing is no preprocessing.
@ -335,13 +443,7 @@ class TTS(metaclass=ABCMeta):
sentence = self.validate_ssml(sentence)
create_signal("isSpeaking")
try:
self._execute(sentence, ident, listen)
except Exception:
# If an error occurs end the audio sequence through an empty entry
self.queue.put(EMPTY_PLAYBACK_QUEUE_TUPLE)
# Re-raise to allow the Exception to be handled externally as well.
raise
self._execute(sentence, ident, listen)
def _execute(self, sentence, ident, listen):
if self.phonetic_spelling:
@ -350,6 +452,8 @@ class TTS(metaclass=ABCMeta):
sentence = sentence.replace(word,
self.spellings[word.lower()])
# TODO: 22.02 This is no longer needed and can be removed
# Just kept for compatibility for now
chunks = self._preprocess_sentence(sentence)
# Apply the listen flag to the last chunk, set the rest to False
chunks = [(chunks[i], listen if i == len(chunks) - 1 else False)
@ -397,7 +501,7 @@ class TTS(metaclass=ABCMeta):
audio_file, phoneme_file
)
viseme = self.viseme(phonemes) if phonemes else None
self.queue.put(
TTS.queue.put(
(self.audio_ext, str(audio_file.path), viseme, ident, l)
)
@ -477,10 +581,6 @@ class TTS(metaclass=ABCMeta):
LOG.debug("Failed to read .PHO from cache")
return None
def __del__(self):
self.playback.stop()
self.playback.join()
class TTSValidator(metaclass=ABCMeta):
"""TTS Validator abstract class to be implemented by all TTS engines.
@ -488,7 +588,6 @@ class TTSValidator(metaclass=ABCMeta):
It exposes and implements ``validate(tts)`` function as a template to
validate the TTS engines.
"""
def __init__(self, tts):
self.tts = tts
@ -560,7 +659,6 @@ class TTSFactory:
from mycroft.tts.spdsay_tts import SpdSay
from mycroft.tts.bing_tts import BingTTS
from mycroft.tts.ibm_tts import WatsonTTS
from mycroft.tts.responsive_voice_tts import ResponsiveVoice
from mycroft.tts.mimic2_tts import Mimic2
from mycroft.tts.yandex_tts import YandexTTS
from mycroft.tts.dummy_tts import DummyTTS
@ -578,7 +676,6 @@ class TTSFactory:
"spdsay": SpdSay,
"watson": WatsonTTS,
"bing": BingTTS,
"responsive_voice": ResponsiveVoice,
"yandex": YandexTTS,
"polly": PollyTTS,
"mozilla": MozillaTTS,

Some files were not shown because too many files have changed in this diff Show More