14 Commits

Author SHA1 Message Date
Michael Hansen d310989555 Fix dictionary with multiple pronunciations 2020-01-07 19:40:59 -05:00
Michael Hansen ed581ecf9d Fixing fuzzywuzzy and others with converters 2020-01-05 20:43:15 -05:00
Michael Hansen f8aedd4ef5 Update Docker update docs 2020-01-05 16:55:37 -05:00
Michael Hansen 14c1386496 Possible fix for threading issues 2020-01-05 16:46:14 -05:00
Michael Hansen 153b642057 Add Rpi Zero to docs 2020-01-05 15:14:45 -05:00
Michael Hansen dec32102dd Merge pull request #146 from esdeboer/freebsd
sed -i is not POSIX compliant, instead make a temp copy and rename to…
2020-01-05 14:50:38 -05:00
Michael Hansen f365c69265 Merge pull request #142 from maxbachmann/cleanup
code cleanup
2020-01-05 14:50:02 -05:00
Michael Hansen 1724c328b7 Trying to fix Docker image 2020-01-05 11:14:56 -05:00
Eric de Boer 6db4a8d341 sed -i is not POSIX compliant, instead make a temp copy and rename to original. 2020-01-05 16:53:38 +01:00
Michael Hansen b70e8a8569 Copying profiles in Docker 2020-01-04 22:41:04 -05:00
Michael Hansen 2e4828da06 Fix dockerignore 2020-01-04 22:34:15 -05:00
Michael Hansen 96cfe69753 Re-generated Dockerfile 2020-01-04 22:02:31 -05:00
maxbachmann 3e8e246c1c swap vars without temp var 2020-01-05 01:02:35 +01:00
Michael Hansen 80a5008b93 Copy built-in slots to Docker 2020-01-04 16:55:38 -05:00
31 changed files with 399 additions and 480 deletions
+171 -22
View File
@@ -1,26 +1,175 @@
.git/
.venv/
node_modules/
__pycache__/
test/
tools/
etc/test/
download/precise-engine/
download/kaldi/
opt/
*
!etc/qemu-*
etc/homeassistant/config/.storage
examples/typical/home-assistant/config/.storage
examples/typical-intent/home-assistant/config/.storage
examples/client-server/home-assistant/config/.storage
examples/mqtt-hermes/home-assistant/config/.storage
!download/rhasspy-tools*
!download/pocketsphinx-python.tar.gz
!download/snowboy*
!download/kaldi*
profiles/*/base_dictionary.txt
profiles/*/base_language_model.txt
profiles/*/acoustic_model/
profiles/*/g2p.fst
!requirements.txt
!dist/
!etc/wav
profiles/en-kaldi/
profiles/en-zamia/
!docker/run.sh
!docker/rhasspy
profiles/*/download/
!profiles/defaults.json
!profiles/zh/profile.json
!profiles/zh/custom_words.txt
!profiles/zh/espeak_phonemes.txt
!profiles/zh/phoneme_examples.txt
!profiles/zh/frequent_words.txt
!profiles/zh/sentences.ini
!profiles/zh/stop_words.txt
!profiles/zh/slots
!profiles/zh/slot_programs
!profiles/hi/profile.json
!profiles/hi/custom_words.txt
!profiles/hi/espeak_phonemes.txt
!profiles/hi/phoneme_examples.txt
!profiles/hi/frequent_words.txt
!profiles/hi/sentences.ini
!profiles/hi/stop_words.txt
!profiles/hi/slots
!profiles/hi/slot_programs
!profiles/el/profile.json
!profiles/el/custom_words.txt
!profiles/el/espeak_phonemes.txt
!profiles/el/phoneme_examples.txt
!profiles/el/frequent_words.txt
!profiles/el/sentences.ini
!profiles/el/stop_words.txt
!profiles/el/slots
!profiles/el/slot_programs
!profiles/es/profile.json
!profiles/es/custom_words.txt
!profiles/es/espeak_phonemes.txt
!profiles/es/phoneme_examples.txt
!profiles/es/frequent_words.txt
!profiles/es/sentences.ini
!profiles/es/stop_words.txt
!profiles/es/slots
!profiles/es/slot_programs
!profiles/it/profile.json
!profiles/it/custom_words.txt
!profiles/it/espeak_phonemes.txt
!profiles/it/phoneme_examples.txt
!profiles/it/frequent_words.txt
!profiles/it/sentences.ini
!profiles/it/stop_words.txt
!profiles/it/slots
!profiles/it/slot_programs
!profiles/ru/profile.json
!profiles/ru/custom_words.txt
!profiles/ru/espeak_phonemes.txt
!profiles/ru/phoneme_examples.txt
!profiles/ru/frequent_words.txt
!profiles/ru/sentences.ini
!profiles/ru/stop_words.txt
!profiles/ru/slots
!profiles/ru/slot_programs
!profiles/pt/profile.json
!profiles/pt/custom_words.txt
!profiles/pt/espeak_phonemes.txt
!profiles/pt/phoneme_examples.txt
!profiles/pt/frequent_words.txt
!profiles/pt/sentences.ini
!profiles/pt/stop_words.txt
!profiles/pt/slots
!profiles/pt/slot_programs
!profiles/sv/profile.json
!profiles/sv/custom_words.txt
!profiles/sv/espeak_phonemes.txt
!profiles/sv/phoneme_examples.txt
!profiles/sv/frequent_words.txt
!profiles/sv/sentences.ini
!profiles/sv/stop_words.txt
!profiles/sv/slots
!profiles/sv/slot_programs
!profiles/vi/profile.json
!profiles/vi/custom_words.txt
!profiles/vi/espeak_phonemes.txt
!profiles/vi/phoneme_examples.txt
!profiles/vi/frequent_words.txt
!profiles/vi/sentences.ini
!profiles/vi/stop_words.txt
!profiles/vi/slots
!profiles/vi/slot_programs
!profiles/ca/profile.json
!profiles/ca/custom_words.txt
!profiles/ca/espeak_phonemes.txt
!profiles/ca/phoneme_examples.txt
!profiles/ca/frequent_words.txt
!profiles/ca/sentences.ini
!profiles/ca/stop_words.txt
!profiles/ca/slots
!profiles/ca/slot_programs
!profiles/nl/profile.json
!profiles/nl/custom_words.txt
!profiles/nl/espeak_phonemes.txt
!profiles/nl/phoneme_examples.txt
!profiles/nl/frequent_words.txt
!profiles/nl/sentences.ini
!profiles/nl/stop_words.txt
!profiles/nl/slots
!profiles/nl/slot_programs
!profiles/nl/kaldi/custom_words.txt
!profiles/nl/kaldi/espeak_phonemes.txt
!profiles/nl/kaldi/phoneme_examples.txt
!profiles/de/profile.json
!profiles/de/custom_words.txt
!profiles/de/espeak_phonemes.txt
!profiles/de/phoneme_examples.txt
!profiles/de/frequent_words.txt
!profiles/de/sentences.ini
!profiles/de/stop_words.txt
!profiles/de/slots
!profiles/de/slot_programs
!profiles/de/kaldi/custom_words.txt
!profiles/de/kaldi/espeak_phonemes.txt
!profiles/de/kaldi/phoneme_examples.txt
!profiles/fr/profile.json
!profiles/fr/custom_words.txt
!profiles/fr/espeak_phonemes.txt
!profiles/fr/phoneme_examples.txt
!profiles/fr/frequent_words.txt
!profiles/fr/sentences.ini
!profiles/fr/stop_words.txt
!profiles/fr/slots
!profiles/fr/slot_programs
!profiles/fr/kaldi/custom_words.txt
!profiles/fr/kaldi/espeak_phonemes.txt
!profiles/fr/kaldi/phoneme_examples.txt
!profiles/en/profile.json
!profiles/en/custom_words.txt
!profiles/en/espeak_phonemes.txt
!profiles/en/phoneme_examples.txt
!profiles/en/frequent_words.txt
!profiles/en/sentences.ini
!profiles/en/stop_words.txt
!profiles/en/slots
!profiles/en/slot_programs
!profiles/en/kaldi/custom_words.txt
!profiles/en/kaldi/espeak_phonemes.txt
!profiles/en/kaldi/phoneme_examples.txt
!rhasspy/profile_schema.json
!rhasspy/*.py
!rhasspy/train/*.py
!rhasspy/train/jsgf2fst/*.py
!*.py
!VERSION
+3 -1
View File
@@ -5,7 +5,9 @@ SHELL := bash
# Docker
# -----------------------------------------------------------------------------
docker: web-dist docker-amd64 docker-armhf docker-aarch64 docker-push manifest
docker: web-dist docker-amd64 docker-armhf docker-aarch64
docker-deploy: docker-push manifest
docker-amd64:
docker build . -f docker/templates/dockerfiles/Dockerfile.prebuilt.alsa.all \
+10 -14
View File
@@ -18,22 +18,18 @@ def main():
profile = json.load(profile_file)
locale_name = profile["locale"] + ".UTF-8"
locale.setlocale(locale.LC_ALL, locale_name)
slots_dir = profile_dir / "slots" / "rhasspy"
slots_dir.mkdir(parents=True, exist_ok=True)
# Day names
with open(slots_dir / "days", "w") as days_file:
for day_num in range(7):
print(calendar.day_name[day_num], file=days_file)
# Month names
with open(slots_dir / "months", "w") as month_file:
for month_num in range(1, 13):
print(calendar.month_name[month_num], file=month_file)
print(locale_name)
slots_dir = profile_dir / "slots" / "rhasspy"
slots_dir.mkdir(parents=True, exist_ok=True)
# Day names
(slots_dir / "days").write_text('\n'.join(calendar.day_name))
# Month names
(slots_dir / "months").write_text('\n'.join(filter(None, calendar.month_name)))
# -----------------------------------------------------------------------------
if __name__ == "__main__":
+14 -5
View File
@@ -329,31 +329,40 @@ case "${CPU_ARCH}" in
esac
requirements_file="${temp_dir}/requirements.txt"
temp_requirements_file="${temp_dir}/temp_requirements.txt"
cp "${this_dir}/requirements.txt" "${requirements_file}"
# Exclude requirements
if [[ -n "${no_flair}" ]]; then
echo "Excluding flair from virtual environment"
sed -i '/^flair/d' "${requirements_file}"
sed '/^flair/d' "${requirements_file}" > "${temp_requirements_file}" &&
mv "${temp_requirements_file}" "${requirements_file}"
fi
if [[ -n "${no_precise}" ]]; then
echo "Excluding Mycroft Precise from virtual environment"
sed -i '/^precise-runner/d' "${requirements_file}"
sed '/^precise-runner/d' "${requirements_file}" > "${temp_requirements_file}" &&
mv "${temp_requirements_file}" "${requirements_file}"
fi
if [[ -n "${no_adapt}" ]]; then
echo "Excluding Mycroft Adapt from virtual environment"
sed -i '/^adapt-parser/d' "${requirements_file}"
sed '/^adapt-parser/d' "${requirements_file}" > "${temp_requirements_file}" &&
mv "${temp_requirements_file}" "${requirements_file}"
fi
if [[ -n "${no_google}" ]]; then
echo "Excluding Google Text to Speech from virtual environment"
sed -i '/^google-cloud-texttospeech/d' "${requirements_file}"
sed '/^google-cloud-texttospeech/d' "${requirements_file}" > "${temp_requirements_file}" &&
mv "${temp_requirements_file}" "${requirements_file}"
fi
# Install everything except openfst first
sed -i '/^openfst/d' "${requirements_file}"
sed '/^openfst/d' "${requirements_file}" > "${temp_requirements_file}" &&
mv "${temp_requirements_file}" "${requirements_file}"
"${python}" -m pip install -r "${requirements_file}"
+1 -132
View File
@@ -1,132 +1 @@
COPY profiles/zh/profile.json \
profiles/zh/custom_words.txt \
profiles/zh/espeak_phonemes.txt \
profiles/zh/phoneme_examples.txt \
profiles/zh/frequent_words.txt \
profiles/zh/sentences.ini \
profiles/zh/stop_words.txt ${RHASSPY_APP}/profiles/zh/
COPY profiles/hi/ \
profiles/hi/profile.json \
profiles/hi/custom_words.txt \
profiles/hi/espeak_phonemes.txt \
profiles/hi/phoneme_examples.txt \
profiles/hi/frequent_words.txt \
profiles/hi/sentences.ini \
profiles/hi/stop_words.txt ${RHASSPY_APP}/profiles/hi/
COPY profiles/el/profile.json \
profiles/el/custom_words.txt \
profiles/el/espeak_phonemes.txt \
profiles/el/phoneme_examples.txt \
profiles/el/frequent_words.txt \
profiles/el/sentences.ini \
profiles/el/stop_words.txt ${RHASSPY_APP}/profiles/el/
COPY profiles/de/profile.json \
profiles/de/custom_words.txt \
profiles/de/espeak_phonemes.txt \
profiles/de/phoneme_examples.txt \
profiles/de/frequent_words.txt \
profiles/de/sentences.ini \
profiles/de/stop_words.txt ${RHASSPY_APP}/profiles/de/
COPY profiles/de/kaldi/custom_words.txt \
profiles/de/kaldi/espeak_phonemes.txt \
profiles/de/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/de/kaldi/
COPY profiles/it/profile.json \
profiles/it/custom_words.txt \
profiles/it/espeak_phonemes.txt \
profiles/it/phoneme_examples.txt \
profiles/it/frequent_words.txt \
profiles/it/sentences.ini \
profiles/it/stop_words.txt ${RHASSPY_APP}/profiles/it/
COPY profiles/es/profile.json \
profiles/es/custom_words.txt \
profiles/es/espeak_phonemes.txt \
profiles/es/phoneme_examples.txt \
profiles/es/frequent_words.txt \
profiles/es/sentences.ini \
profiles/es/stop_words.txt ${RHASSPY_APP}/profiles/es/
COPY profiles/fr/profile.json \
profiles/fr/custom_words.txt \
profiles/fr/espeak_phonemes.txt \
profiles/fr/phoneme_examples.txt \
profiles/fr/frequent_words.txt \
profiles/fr/sentences.ini \
profiles/fr/stop_words.txt ${RHASSPY_APP}/profiles/fr/
COPY profiles/fr/kaldi/custom_words.txt \
profiles/fr/kaldi/espeak_phonemes.txt \
profiles/fr/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/fr/kaldi/
COPY profiles/ru/profile.json \
profiles/ru/custom_words.txt \
profiles/ru/espeak_phonemes.txt \
profiles/ru/phoneme_examples.txt \
profiles/ru/frequent_words.txt \
profiles/ru/sentences.ini \
profiles/ru/stop_words.txt ${RHASSPY_APP}/profiles/ru/
COPY profiles/nl/profile.json \
profiles/nl/custom_words.txt \
profiles/nl/espeak_phonemes.txt \
profiles/nl/phoneme_examples.txt \
profiles/nl/frequent_words.txt \
profiles/nl/sentences.ini \
profiles/nl/stop_words.txt ${RHASSPY_APP}/profiles/nl/
COPY profiles/nl/kaldi/custom_words.txt \
profiles/nl/kaldi/espeak_phonemes.txt \
profiles/nl/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/nl/kaldi/
COPY profiles/vi/profile.json \
profiles/vi/custom_words.txt \
profiles/vi/espeak_phonemes.txt \
profiles/vi/phoneme_examples.txt \
profiles/vi/frequent_words.txt \
profiles/vi/sentences.ini \
profiles/vi/stop_words.txt ${RHASSPY_APP}/profiles/vi/
COPY profiles/pt/profile.json \
profiles/pt/custom_words.txt \
profiles/pt/espeak_phonemes.txt \
profiles/pt/phoneme_examples.txt \
profiles/pt/frequent_words.txt \
profiles/pt/sentences.ini \
profiles/pt/stop_words.txt ${RHASSPY_APP}/profiles/pt/
COPY profiles/sv/profile.json \
profiles/sv/custom_words.txt \
profiles/sv/espeak_phonemes.txt \
profiles/sv/phoneme_examples.txt \
profiles/sv/frequent_words.txt \
profiles/sv/sentences.ini \
profiles/sv/stop_words.txt ${RHASSPY_APP}/profiles/sv/
COPY profiles/ca/profile.json \
profiles/ca/custom_words.txt \
profiles/ca/espeak_phonemes.txt \
profiles/ca/phoneme_examples.txt \
profiles/ca/frequent_words.txt \
profiles/ca/sentences.ini \
profiles/ca/stop_words.txt ${RHASSPY_APP}/profiles/ca/
COPY profiles/en/profile.json \
profiles/en/custom_words.txt \
profiles/en/espeak_phonemes.txt \
profiles/en/phoneme_examples.txt \
profiles/en/frequent_words.txt \
profiles/en/sentences.ini \
profiles/en/stop_words.txt ${RHASSPY_APP}/profiles/en/
COPY profiles/en/kaldi/custom_words.txt \
profiles/en/kaldi/espeak_phonemes.txt \
profiles/en/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/en/kaldi/
COPY profiles/ ${RHASSPY_APP}/profiles/
@@ -72,138 +72,7 @@ RUN chmod +x /run.sh
COPY profiles/zh/profile.json \
profiles/zh/custom_words.txt \
profiles/zh/espeak_phonemes.txt \
profiles/zh/phoneme_examples.txt \
profiles/zh/frequent_words.txt \
profiles/zh/sentences.ini \
profiles/zh/stop_words.txt ${RHASSPY_APP}/profiles/zh/
COPY profiles/hi/ \
profiles/hi/profile.json \
profiles/hi/custom_words.txt \
profiles/hi/espeak_phonemes.txt \
profiles/hi/phoneme_examples.txt \
profiles/hi/frequent_words.txt \
profiles/hi/sentences.ini \
profiles/hi/stop_words.txt ${RHASSPY_APP}/profiles/hi/
COPY profiles/el/profile.json \
profiles/el/custom_words.txt \
profiles/el/espeak_phonemes.txt \
profiles/el/phoneme_examples.txt \
profiles/el/frequent_words.txt \
profiles/el/sentences.ini \
profiles/el/stop_words.txt ${RHASSPY_APP}/profiles/el/
COPY profiles/de/profile.json \
profiles/de/custom_words.txt \
profiles/de/espeak_phonemes.txt \
profiles/de/phoneme_examples.txt \
profiles/de/frequent_words.txt \
profiles/de/sentences.ini \
profiles/de/stop_words.txt ${RHASSPY_APP}/profiles/de/
COPY profiles/de/kaldi/custom_words.txt \
profiles/de/kaldi/espeak_phonemes.txt \
profiles/de/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/de/kaldi/
COPY profiles/it/profile.json \
profiles/it/custom_words.txt \
profiles/it/espeak_phonemes.txt \
profiles/it/phoneme_examples.txt \
profiles/it/frequent_words.txt \
profiles/it/sentences.ini \
profiles/it/stop_words.txt ${RHASSPY_APP}/profiles/it/
COPY profiles/es/profile.json \
profiles/es/custom_words.txt \
profiles/es/espeak_phonemes.txt \
profiles/es/phoneme_examples.txt \
profiles/es/frequent_words.txt \
profiles/es/sentences.ini \
profiles/es/stop_words.txt ${RHASSPY_APP}/profiles/es/
COPY profiles/fr/profile.json \
profiles/fr/custom_words.txt \
profiles/fr/espeak_phonemes.txt \
profiles/fr/phoneme_examples.txt \
profiles/fr/frequent_words.txt \
profiles/fr/sentences.ini \
profiles/fr/stop_words.txt ${RHASSPY_APP}/profiles/fr/
COPY profiles/fr/kaldi/custom_words.txt \
profiles/fr/kaldi/espeak_phonemes.txt \
profiles/fr/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/fr/kaldi/
COPY profiles/ru/profile.json \
profiles/ru/custom_words.txt \
profiles/ru/espeak_phonemes.txt \
profiles/ru/phoneme_examples.txt \
profiles/ru/frequent_words.txt \
profiles/ru/sentences.ini \
profiles/ru/stop_words.txt ${RHASSPY_APP}/profiles/ru/
COPY profiles/nl/profile.json \
profiles/nl/custom_words.txt \
profiles/nl/espeak_phonemes.txt \
profiles/nl/phoneme_examples.txt \
profiles/nl/frequent_words.txt \
profiles/nl/sentences.ini \
profiles/nl/stop_words.txt ${RHASSPY_APP}/profiles/nl/
COPY profiles/nl/kaldi/custom_words.txt \
profiles/nl/kaldi/espeak_phonemes.txt \
profiles/nl/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/nl/kaldi/
COPY profiles/vi/profile.json \
profiles/vi/custom_words.txt \
profiles/vi/espeak_phonemes.txt \
profiles/vi/phoneme_examples.txt \
profiles/vi/frequent_words.txt \
profiles/vi/sentences.ini \
profiles/vi/stop_words.txt ${RHASSPY_APP}/profiles/vi/
COPY profiles/pt/profile.json \
profiles/pt/custom_words.txt \
profiles/pt/espeak_phonemes.txt \
profiles/pt/phoneme_examples.txt \
profiles/pt/frequent_words.txt \
profiles/pt/sentences.ini \
profiles/pt/stop_words.txt ${RHASSPY_APP}/profiles/pt/
COPY profiles/sv/profile.json \
profiles/sv/custom_words.txt \
profiles/sv/espeak_phonemes.txt \
profiles/sv/phoneme_examples.txt \
profiles/sv/frequent_words.txt \
profiles/sv/sentences.ini \
profiles/sv/stop_words.txt ${RHASSPY_APP}/profiles/sv/
COPY profiles/ca/profile.json \
profiles/ca/custom_words.txt \
profiles/ca/espeak_phonemes.txt \
profiles/ca/phoneme_examples.txt \
profiles/ca/frequent_words.txt \
profiles/ca/sentences.ini \
profiles/ca/stop_words.txt ${RHASSPY_APP}/profiles/ca/
COPY profiles/en/profile.json \
profiles/en/custom_words.txt \
profiles/en/espeak_phonemes.txt \
profiles/en/phoneme_examples.txt \
profiles/en/frequent_words.txt \
profiles/en/sentences.ini \
profiles/en/stop_words.txt ${RHASSPY_APP}/profiles/en/
COPY profiles/en/kaldi/custom_words.txt \
profiles/en/kaldi/espeak_phonemes.txt \
profiles/en/kaldi/phoneme_examples.txt \
${RHASSPY_APP}/profiles/en/kaldi/
COPY profiles/ ${RHASSPY_APP}/profiles/
COPY profiles/defaults.json ${RHASSPY_APP}/profiles/
COPY docker/rhasspy ${RHASSPY_APP}/bin/
+4 -1
View File
@@ -4,6 +4,9 @@ Rhasspy is designed to be run on different kinds of hardware, such as:
* Raspberry Pi 2-3 B/B+ (`armhf`/`aarch64`)
* Desktop/laptop/server (`amd64`)
* Raspberry Pi Zero (`armv6l`)
* You must use a [virtual environment](installation.md#virtual-environment)
* The [Kaldi speech recognizer](speech-to-text.md#kaldi) is **not** supported
The table below summarizes architecture compatibility with Rhasspy's components:
@@ -30,7 +33,7 @@ The table below summarizes architecture compatibility with Rhasspy's components:
To run Rhasspy on a Raspberry Pi, you'll need at least a 4 GB SD card and a good power supply. I highly recommend the [CanaKit Starter Kit](https://www.amazon.com/CanaKit-Raspberry-Starter-Premium-Black/dp/B07BCC8PK7), which includes a 32 GB SD card, a 2.5 A power supply, and a case.
Some components of Rhasspy will not work on the Raspberry Pi 3 B+ model (`aarch64`). As of the time of this writing, these are:
Some components of Rhasspy will not work on the Raspberry Pi 3 B+ model with a 64-bit operating system (`aarch64`). As of the time of this writing, these are:
* [snowboy](wake-word.md#snowboy) (wake word)
* [Mycroft Precise](wake-word.md#mycroft-precise) (wake word)
+15 -2
View File
@@ -54,7 +54,13 @@ To update your Rhasspy Docker image, just run:
```bash
docker pull synesthesiam/rhasspy-server:latest
```
on your Rhasspy server and restart the Docker container.
on your Rhasspy server and restart the Docker container. This may require running something like:
```bash
docker rm <container-name>
```
before doing a `docker run...`
## Hass.io
@@ -108,7 +114,14 @@ To update your Rhasspy virtual environment to the latest version, run:
git pull origin master
```
in your `rhasspy` directory. You should also re-build the web interface:
in your `rhasspy` directory, and then update your Python dependencies:
```bash
source .venv/bin/activate
pip3 install -r requirements.txt
```
You should also re-build the web interface:
1. Install [yarn](https://yarnpkg.com) on your system
2. Run `yarn build` in the `rhasspy` directory
+7
View File
@@ -262,6 +262,13 @@ You can pass **arguments** to your program using the syntax `$name,arg1,arg2,...
Like regular slots lists, slot programs can also be put in sub-directories under `slot_programs`. A program in `slot_programs/foo/bar` should be referenced in `sentences.ini` as `$foo/bar`.
#### Built-in Slots
Rhasspy includes a few built-in slots for each language:
* `$rhasspy/days` - day names of the week
* `$rhasspy/months` - month names of the year
### Converters
By default, all named entity values in a recognized intent's JSON are strings. If you need a different data type, such as an integer or float, or want to do some kind of complex *conversion*, use a converter:
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+1 -3
View File
@@ -17,9 +17,7 @@ def main():
step = int(rest_args[0])
if upper < lower:
temp_lower = lower
lower = upper
upper = temp_lower
lower, upper = upper, lower
for n in range(lower, upper + 1, step):
print(n)
+10 -4
View File
@@ -116,9 +116,6 @@ class RhasspyActor:
def stop(self, block=True):
"""Stop this actor and its children."""
for child_actor in self._actors:
child_actor.stop(block=block)
self.send(self, ActorExitRequest())
if block:
self._thread.join()
@@ -127,6 +124,15 @@ class RhasspyActor:
"""Main loop for this actor."""
while self._running:
message_dict = self._queue.get()
message = message_dict.get("message")
if isinstance(message, ActorExitRequest):
for child in self._actors:
self.send(child, ActorExitRequest())
self._running = False
self.transition("stopped")
self.send(self._parent, ChildActorExited(self))
self.on_receive(message_dict)
@property
@@ -296,7 +302,7 @@ class InboxActor(RhasspyActor):
return self
def __exit__(self, *args):
self.stop(block=False)
self.stop(block=True)
class ActorSystem:
+7 -5
View File
@@ -7,8 +7,8 @@ from pathlib import Path
from typing import Any, Dict, List, Optional, Type
import pydash
import pywrapfst as fst
import requests
import rhasspynlu
from rhasspy.actor import (
ActorExitRequest,
@@ -555,12 +555,14 @@ class DialogueManager(RhasspyActor):
self.transition("training_intent")
intent_fst_path = self.profile.read_path(
self.profile.get("intent.fsticuffs.intent_fst", "intent.fst")
intent_graph_path = self.profile.read_path(
self.profile.get("intent.fsticuffs.intent_graph", "intent.json")
)
intent_fst = fst.Fst.read(str(intent_fst_path))
self.send(self.intent_trainer, TrainIntent(intent_fst))
with open(intent_graph_path, "r") as graph_file:
json_graph = json.load(graph_file)
intent_graph = rhasspynlu.json_to_graph(json_graph)
self.send(self.intent_trainer, TrainIntent(intent_graph))
except Exception as e:
self.transition("ready")
self.send(self.training_receiver, ProfileTrainingFailed(str(e)))
+2 -2
View File
@@ -246,8 +246,8 @@ class IntentForwarded:
class TrainIntent:
"""Request to train intent recognizer."""
def __init__(self, intent_fst, receiver: Optional[RhasspyActor] = None) -> None:
self.intent_fst = intent_fst
def __init__(self, intent_graph, receiver: Optional[RhasspyActor] = None) -> None:
self.intent_graph = intent_graph
self.receiver = receiver
+2 -56
View File
@@ -1,13 +1,11 @@
"""Support for intent recognition."""
import concurrent.futures
import io
import json
import logging
import os
import re
import shutil
import subprocess
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type
from urllib.parse import urljoin
@@ -18,7 +16,7 @@ from rhasspynlu import json_to_graph, recognize
from rhasspy.actor import RhasspyActor
from rhasspy.events import IntentRecognized, RecognizeIntent, SpeakSentence
from rhasspy.utils import empty_intent, hass_request_kwargs
from rhasspy.utils import empty_intent, hass_request_kwargs, load_converters
# -----------------------------------------------------------------------------
@@ -137,32 +135,6 @@ class RemoteRecognizer(RhasspyActor):
# -----------------------------------------------------------------------------
class CliConverter:
"""Command-line converter for intent recognition"""
def __init__(self, name: str, command_path: Path):
self.name = name
self.command_path = command_path
def __call__(self, *args, converter_args=None):
"""Runs external program to convert JSON values"""
converter_args = converter_args or []
proc = subprocess.Popen(
[str(self.command_path)] + converter_args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
universal_newlines=True,
)
with io.StringIO() as input_file:
for arg in args:
json.dump(arg, input_file)
stdout, _ = proc.communicate(input=input_file.getvalue())
return [json.loads(line) for line in stdout.splitlines() if line.strip()]
class FsticuffsRecognizer(RhasspyActor):
"""Recognize intents using OpenFST."""
@@ -188,33 +160,7 @@ class FsticuffsRecognizer(RhasspyActor):
self.fuzzy = self.profile.get("intent.fsticuffs.fuzzy", True)
# Load user-defined converters
converters_dir = Path(
self.profile.read_path(
self.profile.get("intent.fsticuffs.converters_dir", "converters")
)
)
if converters_dir.is_dir():
self._logger.debug("Loading converters from %s", converters_dir)
for converter_path in converters_dir.glob("**/*"):
if not converter_path.is_file():
continue
# Retain directory structure in name
converter_name = str(
converter_path.relative_to(converters_dir).with_suffix("")
)
# Run converter as external program.
# Input arguments are encoded as JSON on individual lines.
# Output values should be encoded as JSON on individual lines.
converter = CliConverter(converter_name, converter_path)
# Key off name without file extension
self.converters[converter_name] = converter
self._logger.debug(
"Loaded converter %s from %s", converter_name, converter_path
)
self.converters = load_converters(self.profile)
self.transition("loaded")
+38 -12
View File
@@ -9,14 +9,12 @@ import tempfile
import time
from collections import Counter, defaultdict
from io import StringIO
from typing import Any, Dict, List, Set, Type
from typing import Any, Callable, Dict, List, Set, Type
from urllib.parse import urljoin
from rhasspy.actor import RhasspyActor
from rhasspy.events import (IntentTrainingComplete, IntentTrainingFailed,
TrainIntent)
from rhasspy.utils import (lcm, make_sentences_by_intent,
sample_sentences_by_intent)
from rhasspy.events import IntentTrainingComplete, IntentTrainingFailed, TrainIntent
from rhasspy.utils import lcm, make_sentences_by_intent, load_converters
# -----------------------------------------------------------------------------
@@ -112,23 +110,30 @@ class FsticuffsIntentTrainer(DummyIntentTrainer):
class FuzzyWuzzyIntentTrainer(RhasspyActor):
"""Save examples to JSON for fuzzy string matching later."""
def __init__(self):
RhasspyActor.__init__(self)
self.converters: Dict[str, Callable[..., Any]] = {}
def in_started(self, message: Any, sender: RhasspyActor) -> None:
"""Handle messages in started state."""
if isinstance(message, TrainIntent):
try:
self.train(message.intent_fst)
self.train(message.intent_graph)
self.send(message.receiver or sender, IntentTrainingComplete())
except Exception as e:
self._logger.exception("train")
self.send(message.receiver or sender, IntentTrainingFailed(repr(e)))
def train(self, intent_fst) -> None:
def train(self, intent_graph) -> None:
"""Save examples to JSON file."""
examples_path = self.profile.write_path(
self.profile.get("intent.fuzzywuzzy.examples_json")
)
sentences_by_intent: Dict[str, Any] = make_sentences_by_intent(intent_fst)
converters = load_converters(self.profile)
sentences_by_intent = make_sentences_by_intent(
intent_graph, extra_converters=converters
)
with open(examples_path, "w") as examples_file:
json.dump(sentences_by_intent, examples_file, indent=4)
@@ -144,6 +149,10 @@ class FuzzyWuzzyIntentTrainer(RhasspyActor):
class RasaIntentTrainer(RhasspyActor):
"""Uses Rasa NLU HTTP API to train a recognizer."""
def __init__(self):
RhasspyActor.__init__(self)
self.converters: Dict[str, Callable[..., Any]] = {}
def in_started(self, message: Any, sender: RhasspyActor) -> None:
"""Handle messages in started state."""
if isinstance(message, TrainIntent):
@@ -256,7 +265,9 @@ class RasaIntentTrainer(RhasspyActor):
response.raise_for_status()
except Exception:
# Rasa gives quite helpful error messages, so extract them from the response.
raise Exception(f'{response.reason}: {json.loads(response.content)["message"]}')
raise Exception(
f'{response.reason}: {json.loads(response.content)["message"]}'
)
# -----------------------------------------------------------------------------
@@ -268,6 +279,14 @@ class RasaIntentTrainer(RhasspyActor):
class AdaptIntentTrainer(RhasspyActor):
"""Configure a Mycroft Adapt engine."""
def __init__(self):
RhasspyActor.__init__(self)
self.converters: Dict[str, Callable[..., Any]] = {}
def to_started(self, from_state: str) -> None:
# Load user-defined converters
self.converters = load_converters(self.profile)
def in_started(self, message: Any, sender: RhasspyActor) -> None:
"""Handle messages in started state."""
if isinstance(message, TrainIntent):
@@ -287,9 +306,7 @@ class AdaptIntentTrainer(RhasspyActor):
stop_words_path = self.profile.read_path("stop_words.txt")
if os.path.exists(stop_words_path):
with open(stop_words_path, "r") as stop_words_file:
stop_words = {
line.strip() for line in stop_words_file if line.strip()
}
stop_words = {line.strip() for line in stop_words_file if line.strip()}
# { intent: [ { 'text': ..., 'entities': { ... } }, ... ] }
sentences_by_intent: Dict[str, Any] = make_sentences_by_intent(intent_fst)
@@ -410,6 +427,11 @@ class FlairIntentTrainer(RhasspyActor):
def __init__(self):
RhasspyActor.__init__(self)
self.embeddings = []
self.converters: Dict[str, Callable[..., Any]] = {}
def to_started(self, from_state: str) -> None:
# Load user-defined converters
self.converters = load_converters(self.profile)
def in_started(self, message: Any, sender: RhasspyActor) -> None:
"""Handle messages in started state."""
@@ -671,6 +693,7 @@ class CommandIntentTrainer(RhasspyActor):
def __init__(self):
RhasspyActor.__init__(self)
self.command: List[str] = []
self.converters: Dict[str, Callable[..., Any]] = {}
def to_started(self, from_state: str) -> None:
"""Transition to started state."""
@@ -682,6 +705,9 @@ class CommandIntentTrainer(RhasspyActor):
for a in self.profile.get("training.intent.command.arguments", [])
]
# Load user-defined converters
self.converters = load_converters(self.profile)
self.command = [program] + arguments
def in_started(self, message: Any, sender: RhasspyActor) -> None:
+3 -5
View File
@@ -165,9 +165,7 @@ def train_profile(profile_dir: Path, profile: Profile) -> Tuple[int, List[str]]:
# Check for arguments.
# Slot name retains argument(s).
if "," in slot_name:
parts = slot_name.split(",")
slot_name = parts[0]
slot_args = parts[1:]
slot_name, *slot_args = slot_name.split(",")
else:
slot_args = None
@@ -228,7 +226,7 @@ def train_profile(profile_dir: Path, profile: Profile) -> Tuple[int, List[str]]:
upper_bound = int(match.group(2))
step = 1
if len(match.groups()) > 2:
if len(match.groups()) > 3:
# Exclude ,
step = int(match.group(3)[1:])
@@ -270,7 +268,7 @@ def train_profile(profile_dir: Path, profile: Profile) -> Tuple[int, List[str]]:
word.converters = ["int"]
return word
# Hard case, split into mutliple Words
# Hard case, split into multiple Words
return jsgf.Sequence(
text=number_text,
type=jsgf.SequenceType.GROUP,
+1 -1
View File
@@ -91,7 +91,7 @@ def make_dict(
if (i < 1) or no_number:
print(word, pronounce, file=dictionary_file)
else:
print(f"{word, i + 1}({pronounce})", file=dictionary_file)
print(f"{word}({i + 1})", pronounce, file=dictionary_file)
words_in_dict.add(word)
+96 -44
View File
@@ -1,23 +1,24 @@
"""Rhasspy utility functions."""
import collections
import concurrent.futures
import gzip
import io
import itertools
import json
import logging
import math
import os
import random
import re
import subprocess
import threading
import wave
from collections import defaultdict
from pathlib import Path
from typing import (Any, Callable, Dict, Iterable, List, Mapping, Optional,
Set, Tuple)
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Set, Tuple
import pywrapfst as fst
import networkx as nx
import rhasspynlu
from num2words import num2words
WHITESPACE_PATTERN = re.compile(r"\s+")
@@ -329,55 +330,45 @@ def grouper(iterable, n, fillvalue=None):
# -----------------------------------------------------------------------------
def make_sentences_by_intent(intent_fst: fst.Fst) -> Dict[str, Any]:
"""Get all sentences from an FST."""
from rhasspy.train.jsgf2fst import fstprintall, symbols2intent
def make_sentences_by_intent(
intent_graph: nx.DiGraph, num_samples: Optional[int] = None, extra_converters=None
) -> Dict[str, List[Dict[str, Any]]]:
"""Get all sentences from a graph."""
# { intent: [ { 'text': ..., 'entities': { ... } }, ... ] }
sentences_by_intent: Dict[str, Any] = defaultdict(list)
for symbols in fstprintall(intent_fst, exclude_meta=False):
intent = symbols2intent(symbols)
intent_name = intent["intent"]["name"]
sentences_by_intent[intent_name].append(intent)
start_node = None
end_node = None
for node, node_data in intent_graph.nodes(data=True):
if node_data.get("start", False):
start_node = node
elif node_data.get("final", False):
end_node = node
return sentences_by_intent
if start_node and end_node:
break
assert (start_node is not None) and (
end_node is not None
), "Missing start/end node(s)"
# -----------------------------------------------------------------------------
def sample_sentences_by_intent(
intent_fst_paths: Dict[str, str], num_samples: int
) -> Dict[str, Any]:
"""Generate random intents"""
from rhasspy.train.jsgf2fst import fstprintall, symbols2intent
def sample_sentences(intent_name: str, intent_fst_path: str):
rand_fst = fst.Fst.read_from_string(
subprocess.check_output(
["fstrandgen", f"--npath={num_samples}", intent_fst_path]
)
if num_samples is not None:
# Randomly sample
paths = random.sample(
list(nx.all_simple_paths(intent_graph, start_node, end_node)), num_samples
)
else:
# Use generator
paths = nx.all_simple_paths(intent_graph, start_node, end_node)
sentences: List[Dict[str, Any]] = []
for symbols in fstprintall(rand_fst, exclude_meta=False):
intent = symbols2intent(symbols)
sentences.append(intent)
return sentences
# Generate samples in parallel
future_to_intent = {}
with concurrent.futures.ThreadPoolExecutor() as executor:
for intent_name, intent_fst_path in intent_fst_paths.items():
future = executor.submit(sample_sentences, intent_name, intent_fst_path)
future_to_intent[future] = intent_name
# { intent: [ { 'text': ..., 'entities': { ... } }, ... ] }
sentences_by_intent: Dict[str, Any] = {}
for future, intent_name in future_to_intent.items():
sentences_by_intent[intent_name] = future.result()
# TODO: Add converters
for path in paths:
_, recognition = rhasspynlu.fsticuffs.path_to_recognition(
path, intent_graph, extra_converters=extra_converters
)
assert recognition, "Path failed"
sentences_by_intent[recognition.intent.name].append(recognition.asdict())
return sentences_by_intent
@@ -507,3 +498,64 @@ def get_all_intents(ini_paths: List[Path]) -> Dict[str, Any]:
_LOGGER.exception("Failed to parse %s", ini_paths)
return {}
# -----------------------------------------------------------------------------
class CliConverter:
"""Command-line converter for intent recognition"""
def __init__(self, name: str, command_path: Path):
self.name = name
self.command_path = command_path
def __call__(self, *args, converter_args=None):
"""Runs external program to convert JSON values"""
converter_args = converter_args or []
proc = subprocess.Popen(
[str(self.command_path)] + converter_args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
universal_newlines=True,
)
with io.StringIO() as input_file:
for arg in args:
json.dump(arg, input_file)
stdout, _ = proc.communicate(input=input_file.getvalue())
return [json.loads(line) for line in stdout.splitlines() if line.strip()]
def load_converters(profile) -> Dict[str, Any]:
# Load user-defined converters
converters = {}
converters_dir = Path(
profile.read_path(profile.get("intent.fsticuffs.converters_dir", "converters"))
)
if converters_dir.is_dir():
_LOGGER.debug("Loading converters from %s", converters_dir)
for converter_path in converters_dir.glob("**/*"):
if not converter_path.is_file():
continue
# Retain directory structure in name
converter_name = str(
converter_path.relative_to(converters_dir).with_suffix("")
)
# Run converter as external program.
# Input arguments are encoded as JSON on individual lines.
# Output values should be encoded as JSON on individual lines.
converter = CliConverter(converter_name, converter_path)
# Key off name without file extension
converters[converter_name] = converter
_LOGGER.debug("Loaded converter %s from %s", converter_name, converter_path)
return converters