Release v0.2.0 — VAD + YAML plugin commands
This commit is contained in:
commit
f45ac77a2e
5 changed files with 374 additions and 244 deletions
43
README.md
43
README.md
|
|
@ -49,10 +49,51 @@ python main.py
|
||||||
## Использование
|
## Использование
|
||||||
|
|
||||||
1. Скажите **«Джарвис»** (или **«jarvis»**) — ассистент даст звуковой сигнал готовности.
|
1. Скажите **«Джарвис»** (или **«jarvis»**) — ассистент даст звуковой сигнал готовности.
|
||||||
2. В течение 10 секунд произнесите команду:
|
2. Произнесите команду — окно записи закрывается само, как только вы замолчите (см. ниже про VAD):
|
||||||
- либо одну из фраз из `commands.yaml` (открыть браузер, выключить звук и т.д.);
|
- либо одну из фраз из `commands.yaml` (открыть браузер, выключить звук и т.д.);
|
||||||
- либо начните со слова **«скажи …»** — остаток фразы уйдёт в Groq, ответ будет зачитан вслух.
|
- либо начните со слова **«скажи …»** — остаток фразы уйдёт в Groq, ответ будет зачитан вслух.
|
||||||
|
|
||||||
|
## VAD (определение конца команды)
|
||||||
|
|
||||||
|
Окно записи команды закрывается по голосовой активности (webrtcvad), а не по таймеру. Параметры в `config.py`:
|
||||||
|
|
||||||
|
- `VAD_AGGRESSIVENESS` (0..3) — насколько агрессивно VAD режет шум; по умолчанию 2.
|
||||||
|
- `COMMAND_END_SILENCE_MS` — длительность тишины, после которой окно закрывается (по умолчанию 1200 мс).
|
||||||
|
- `COMMAND_MIN_SPEECH_MS` — минимальная длительность речи, иначе тишина не считается концом (500 мс).
|
||||||
|
- `COMMAND_MIN_LISTEN_MS` — минимальное время записи, чтобы не закрыться мгновенно (1000 мс).
|
||||||
|
- `COMMAND_MAX_LISTEN_MS` — жёсткий потолок (15000 мс).
|
||||||
|
|
||||||
|
## Схема commands.yaml
|
||||||
|
|
||||||
|
Каждая команда — это пара `phrases` + `action`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
open_browser:
|
||||||
|
phrases:
|
||||||
|
- открой браузер
|
||||||
|
- запусти браузер
|
||||||
|
action: {type: exe, file: "Run browser.exe"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Типы действий:
|
||||||
|
|
||||||
|
- `exe` — запустить `.exe` из `custom-commands/`. Поля: `file` (имя файла), необязательные `wait: true` (ждать завершения), `delay_ms` (пауза после запуска). По умолчанию после `exe` проигрывается звук подтверждения `ok` — отключается через `confirm_sound: null`.
|
||||||
|
- `play_sound` — проиграть WAV из `sound/`. Поле `name` (без расширения). Имена `greet` и `ok` проигрываются с рандомным выбором из вариантов `greet1/2/3.wav`, `ok1/2/3.wav`.
|
||||||
|
- `system` — встроенные операции. `op: volume_mute`, `volume_unmute`, `exit`.
|
||||||
|
- `multi` — последовательность шагов: `steps: [...]`.
|
||||||
|
|
||||||
|
Шаги внутри `multi` — тех же типов. Пример:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sound_off:
|
||||||
|
phrases: [выключи звук, отключи звук]
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: ok}
|
||||||
|
- {type: system, op: volume_mute}
|
||||||
|
```
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
CC BY-NC-SA 4.0 — см. [LICENSE.txt](LICENSE.txt). Авторство оригинала — Abraham Tugalov / Priler. Изменения в этом форке — Bossiara13 (Dmitry Bykov), 2026.
|
CC BY-NC-SA 4.0 — см. [LICENSE.txt](LICENSE.txt). Авторство оригинала — Abraham Tugalov / Priler. Изменения в этом форке — Bossiara13 (Dmitry Bykov), 2026.
|
||||||
|
|
|
||||||
386
commands.yaml
386
commands.yaml
|
|
@ -1,157 +1,239 @@
|
||||||
gaming_mode_off:
|
|
||||||
- рабочий режим
|
|
||||||
- вернись в рабочий режим
|
|
||||||
- переключись на компьютер
|
|
||||||
- обратно на компьютер
|
|
||||||
- отключи игровой режим
|
|
||||||
- выйди из игрового режима
|
|
||||||
- вернись на компьютер
|
|
||||||
- выход с игрового режима
|
|
||||||
- рабочее пространство
|
|
||||||
- вернись в игровой режим
|
|
||||||
- выключи игровой режим
|
|
||||||
gaming_mode_on:
|
|
||||||
- включи игровой режим
|
|
||||||
- перейди в игровой режим
|
|
||||||
- я хочу поиграть
|
|
||||||
- переключись на телевизор
|
|
||||||
music:
|
|
||||||
- давай музыку
|
|
||||||
- музыка
|
|
||||||
- яндекс музыка
|
|
||||||
- включи музыку
|
|
||||||
- открой музыку
|
|
||||||
- послушаем музыку
|
|
||||||
- хочу музыку
|
|
||||||
- врубай музло
|
|
||||||
- включи что-то послушать
|
|
||||||
- давай что-то послушаем
|
|
||||||
music_next:
|
|
||||||
- следующий трек
|
|
||||||
- следующая мелодия
|
|
||||||
- следующая песня
|
|
||||||
- переключи трек
|
|
||||||
- переключи музыку
|
|
||||||
- переключи мелодию
|
|
||||||
- переключи песню
|
|
||||||
- следующая музыка
|
|
||||||
music_off:
|
|
||||||
- выключи музыку
|
|
||||||
- отруби музыку
|
|
||||||
- убери музыку
|
|
||||||
- отключи музыку
|
|
||||||
- закрой музыку
|
|
||||||
- останови музыку
|
|
||||||
- хватит музыки
|
|
||||||
music_prev:
|
|
||||||
- предыдущая музыка
|
|
||||||
- предыдущий трек
|
|
||||||
- предыдущая мелодия
|
|
||||||
- предыдущая песня
|
|
||||||
- верни трек
|
|
||||||
- верни мелодию
|
|
||||||
- верни песню
|
|
||||||
- верни прошлый трек
|
|
||||||
- верни прошлую мелодию
|
|
||||||
- верни прошлую песню
|
|
||||||
- верни прошлую музыку
|
|
||||||
- верни то что играло
|
|
||||||
- давай предыдущий трек
|
|
||||||
- переключи музыку обратно
|
|
||||||
- переключи на прошлый трек
|
|
||||||
- переключи на прошлую мелодию
|
|
||||||
- переключи на прошлую песню
|
|
||||||
music_save:
|
|
||||||
- сохрани трек
|
|
||||||
- мне нравится трек
|
|
||||||
- сохрани песню
|
|
||||||
- мне нравится песня
|
|
||||||
- добавь трек в избранное
|
|
||||||
- добавь песню в избранное
|
|
||||||
- запомни мелодию
|
|
||||||
- запомни трек
|
|
||||||
- запомни песню
|
|
||||||
- добавь мелодию в избранное
|
|
||||||
- сохрани то что сейчас играет
|
|
||||||
- добавь то что сейчас играет в избранное
|
|
||||||
- лайкни трек
|
|
||||||
- лайкни мелодию
|
|
||||||
- лайкни песню
|
|
||||||
'off':
|
|
||||||
- выключись
|
|
||||||
- вырубись
|
|
||||||
- завершить работу
|
|
||||||
- закройся
|
|
||||||
- отключись
|
|
||||||
- заверши свою работу
|
|
||||||
- на сегодня хватит
|
|
||||||
- выгрузи себя из памяти
|
|
||||||
- ты мне надоел
|
|
||||||
- пора спать
|
|
||||||
open_browser:
|
open_browser:
|
||||||
- открой браузер
|
phrases:
|
||||||
- запусти браузер
|
- открой браузер
|
||||||
- открой гугл хром
|
- запусти браузер
|
||||||
- гугл хром
|
- открой гугл хром
|
||||||
open_google:
|
- гугл хром
|
||||||
- открой гугл
|
action: {type: exe, file: "Run browser.exe"}
|
||||||
- гугл
|
|
||||||
- запусти гугл
|
|
||||||
open_youtube:
|
open_youtube:
|
||||||
- открой ютуб
|
phrases:
|
||||||
- ютуб
|
- открой ютуб
|
||||||
- запусти ютуб
|
- ютуб
|
||||||
|
- запусти ютуб
|
||||||
|
action: {type: exe, file: "Run youtube.exe"}
|
||||||
|
|
||||||
|
open_google:
|
||||||
|
phrases:
|
||||||
|
- открой гугл
|
||||||
|
- гугл
|
||||||
|
- запусти гугл
|
||||||
|
action: {type: exe, file: "Run google.exe"}
|
||||||
|
|
||||||
|
music:
|
||||||
|
phrases:
|
||||||
|
- давай музыку
|
||||||
|
- музыка
|
||||||
|
- яндекс музыка
|
||||||
|
- включи музыку
|
||||||
|
- открой музыку
|
||||||
|
- послушаем музыку
|
||||||
|
- хочу музыку
|
||||||
|
- врубай музло
|
||||||
|
- включи что-то послушать
|
||||||
|
- давай что-то послушаем
|
||||||
|
action: {type: exe, file: "Run music.exe"}
|
||||||
|
|
||||||
|
music_off:
|
||||||
|
phrases:
|
||||||
|
- выключи музыку
|
||||||
|
- отруби музыку
|
||||||
|
- убери музыку
|
||||||
|
- отключи музыку
|
||||||
|
- закрой музыку
|
||||||
|
- останови музыку
|
||||||
|
- хватит музыки
|
||||||
|
action: {type: exe, file: "Stop music.exe", delay_ms: 200}
|
||||||
|
|
||||||
|
music_save:
|
||||||
|
phrases:
|
||||||
|
- сохрани трек
|
||||||
|
- мне нравится трек
|
||||||
|
- сохрани песню
|
||||||
|
- мне нравится песня
|
||||||
|
- добавь трек в избранное
|
||||||
|
- добавь песню в избранное
|
||||||
|
- запомни мелодию
|
||||||
|
- запомни трек
|
||||||
|
- запомни песню
|
||||||
|
- добавь мелодию в избранное
|
||||||
|
- сохрани то что сейчас играет
|
||||||
|
- добавь то что сейчас играет в избранное
|
||||||
|
- лайкни трек
|
||||||
|
- лайкни мелодию
|
||||||
|
- лайкни песню
|
||||||
|
action: {type: exe, file: "Save music.exe", delay_ms: 200}
|
||||||
|
|
||||||
|
music_next:
|
||||||
|
phrases:
|
||||||
|
- следующий трек
|
||||||
|
- следующая мелодия
|
||||||
|
- следующая песня
|
||||||
|
- переключи трек
|
||||||
|
- переключи музыку
|
||||||
|
- переключи мелодию
|
||||||
|
- переключи песню
|
||||||
|
- следующая музыка
|
||||||
|
action: {type: exe, file: "Next music.exe", delay_ms: 200}
|
||||||
|
|
||||||
|
music_prev:
|
||||||
|
phrases:
|
||||||
|
- предыдущая музыка
|
||||||
|
- предыдущий трек
|
||||||
|
- предыдущая мелодия
|
||||||
|
- предыдущая песня
|
||||||
|
- верни трек
|
||||||
|
- верни мелодию
|
||||||
|
- верни песню
|
||||||
|
- верни прошлый трек
|
||||||
|
- верни прошлую мелодию
|
||||||
|
- верни прошлую песню
|
||||||
|
- верни прошлую музыку
|
||||||
|
- верни то что играло
|
||||||
|
- давай предыдущий трек
|
||||||
|
- переключи музыку обратно
|
||||||
|
- переключи на прошлый трек
|
||||||
|
- переключи на прошлую мелодию
|
||||||
|
- переключи на прошлую песню
|
||||||
|
action: {type: exe, file: "Prev music.exe", delay_ms: 200}
|
||||||
|
|
||||||
sound_off:
|
sound_off:
|
||||||
- выключи звук
|
phrases:
|
||||||
- беззвучный режим
|
- выключи звук
|
||||||
- режим без звука
|
- беззвучный режим
|
||||||
- отключи звук
|
- режим без звука
|
||||||
|
- отключи звук
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: ok}
|
||||||
|
- {type: system, op: volume_mute}
|
||||||
|
|
||||||
sound_on:
|
sound_on:
|
||||||
- включи звук
|
phrases:
|
||||||
- режим со звуком
|
- включи звук
|
||||||
- верни звук
|
- режим со звуком
|
||||||
stupid:
|
- верни звук
|
||||||
- ты дурак
|
action:
|
||||||
- ты дебил
|
type: multi
|
||||||
- ты глупый
|
steps:
|
||||||
- ты тупой
|
- {type: system, op: volume_unmute}
|
||||||
switch_to_dynamics:
|
- {type: play_sound, name: ok}
|
||||||
- переключи на динамики
|
|
||||||
- включи динамики
|
|
||||||
- перейди на динамики
|
|
||||||
- давай звук в динамики
|
|
||||||
- давай звук на динамики
|
|
||||||
- переключи звук в динамики
|
|
||||||
- переключи звук на динамики
|
|
||||||
- переключи на колонки
|
|
||||||
- включи колонки
|
|
||||||
- перейди на колонки
|
|
||||||
- давай звук в колонки
|
|
||||||
- давай звук на колонки
|
|
||||||
- переключи звук в колонки
|
|
||||||
- переключи звук на колонки
|
|
||||||
switch_to_headphones:
|
|
||||||
- переключи на наушники
|
|
||||||
- включи наушники
|
|
||||||
- перейди на наушники
|
|
||||||
- давай звук в наушники
|
|
||||||
- давай звук на наушники
|
|
||||||
- переключи звук в наушники
|
|
||||||
- переключи звук на наушники
|
|
||||||
- переключи на кракены
|
|
||||||
- включи кракены
|
|
||||||
- перейди на кракены
|
|
||||||
- давай звук в кракены
|
|
||||||
- давай звук на кракены
|
|
||||||
- переключи звук в кракены
|
|
||||||
- переключи звук на кракены
|
|
||||||
thanks:
|
thanks:
|
||||||
- спасибо
|
phrases:
|
||||||
- молодец
|
- спасибо
|
||||||
- респект
|
- молодец
|
||||||
- ты супер
|
- респект
|
||||||
- отличная работа
|
- ты супер
|
||||||
- ты крут
|
- отличная работа
|
||||||
- ты большой молодец
|
- ты крут
|
||||||
- ты реально крут
|
- ты большой молодец
|
||||||
- ты афигенный
|
- ты реально крут
|
||||||
|
- ты афигенный
|
||||||
|
action: {type: play_sound, name: thanks}
|
||||||
|
|
||||||
|
stupid:
|
||||||
|
phrases:
|
||||||
|
- ты дурак
|
||||||
|
- ты дебил
|
||||||
|
- ты глупый
|
||||||
|
- ты тупой
|
||||||
|
action: {type: play_sound, name: stupid}
|
||||||
|
|
||||||
|
gaming_mode_on:
|
||||||
|
phrases:
|
||||||
|
- включи игровой режим
|
||||||
|
- перейди в игровой режим
|
||||||
|
- я хочу поиграть
|
||||||
|
- переключись на телевизор
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: ok}
|
||||||
|
- {type: exe, file: "Switch to gaming mode.exe", wait: true}
|
||||||
|
- {type: play_sound, name: ready}
|
||||||
|
|
||||||
|
gaming_mode_off:
|
||||||
|
phrases:
|
||||||
|
- рабочий режим
|
||||||
|
- вернись в рабочий режим
|
||||||
|
- переключись на компьютер
|
||||||
|
- обратно на компьютер
|
||||||
|
- отключи игровой режим
|
||||||
|
- выйди из игрового режима
|
||||||
|
- вернись на компьютер
|
||||||
|
- выход с игрового режима
|
||||||
|
- рабочее пространство
|
||||||
|
- вернись в игровой режим
|
||||||
|
- выключи игровой режим
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: ok}
|
||||||
|
- {type: exe, file: "Switch back to workspace.exe", wait: true}
|
||||||
|
- {type: play_sound, name: ready}
|
||||||
|
|
||||||
|
switch_to_headphones:
|
||||||
|
phrases:
|
||||||
|
- переключи на наушники
|
||||||
|
- включи наушники
|
||||||
|
- перейди на наушники
|
||||||
|
- давай звук в наушники
|
||||||
|
- давай звук на наушники
|
||||||
|
- переключи звук в наушники
|
||||||
|
- переключи звук на наушники
|
||||||
|
- переключи на кракены
|
||||||
|
- включи кракены
|
||||||
|
- перейди на кракены
|
||||||
|
- давай звук в кракены
|
||||||
|
- давай звук на кракены
|
||||||
|
- переключи звук в кракены
|
||||||
|
- переключи звук на кракены
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: ok}
|
||||||
|
- {type: exe, file: "Switch to headphones.exe", wait: true, delay_ms: 500}
|
||||||
|
- {type: play_sound, name: ready}
|
||||||
|
|
||||||
|
switch_to_dynamics:
|
||||||
|
phrases:
|
||||||
|
- переключи на динамики
|
||||||
|
- включи динамики
|
||||||
|
- перейди на динамики
|
||||||
|
- давай звук в динамики
|
||||||
|
- давай звук на динамики
|
||||||
|
- переключи звук в динамики
|
||||||
|
- переключи звук на динамики
|
||||||
|
- переключи на колонки
|
||||||
|
- включи колонки
|
||||||
|
- перейди на колонки
|
||||||
|
- давай звук в колонки
|
||||||
|
- давай звук на колонки
|
||||||
|
- переключи звук в колонки
|
||||||
|
- переключи звук на колонки
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: ok}
|
||||||
|
- {type: exe, file: "Switch to dynamics.exe", wait: true, delay_ms: 500}
|
||||||
|
- {type: play_sound, name: ready}
|
||||||
|
|
||||||
|
'off':
|
||||||
|
phrases:
|
||||||
|
- выключись
|
||||||
|
- вырубись
|
||||||
|
- завершить работу
|
||||||
|
- закройся
|
||||||
|
- отключись
|
||||||
|
- заверши свою работу
|
||||||
|
- на сегодня хватит
|
||||||
|
- выгрузи себя из памяти
|
||||||
|
- ты мне надоел
|
||||||
|
- пора спать
|
||||||
|
action:
|
||||||
|
type: multi
|
||||||
|
steps:
|
||||||
|
- {type: play_sound, name: "off"}
|
||||||
|
- {type: system, op: exit}
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,11 @@ CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
|
||||||
GROQ_TOKEN = os.getenv('GROQ_TOKEN')
|
GROQ_TOKEN = os.getenv('GROQ_TOKEN')
|
||||||
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
||||||
GROQ_MODEL = "llama-3.3-70b-versatile"
|
GROQ_MODEL = "llama-3.3-70b-versatile"
|
||||||
|
|
||||||
|
# VAD (webrtcvad) — определяет конец команды по тишине вместо фиксированных 10 сек.
|
||||||
|
# 0..3, выше — агрессивнее режет шум (но и обрезает речь).
|
||||||
|
VAD_AGGRESSIVENESS = 2
|
||||||
|
COMMAND_END_SILENCE_MS = 1200
|
||||||
|
COMMAND_MIN_SPEECH_MS = 500
|
||||||
|
COMMAND_MIN_LISTEN_MS = 1000
|
||||||
|
COMMAND_MAX_LISTEN_MS = 15000
|
||||||
|
|
|
||||||
178
main.py
178
main.py
|
|
@ -13,6 +13,7 @@ import openai
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
import simpleaudio as sa
|
import simpleaudio as sa
|
||||||
import vosk
|
import vosk
|
||||||
|
import webrtcvad
|
||||||
import yaml
|
import yaml
|
||||||
from comtypes import CLSCTX_ALL
|
from comtypes import CLSCTX_ALL
|
||||||
from fuzzywuzzy import fuzz
|
from fuzzywuzzy import fuzz
|
||||||
|
|
@ -54,6 +55,10 @@ wake_rec = vosk.KaldiRecognizer(model, samplerate, wake_grammar)
|
||||||
kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
|
kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
|
||||||
q = queue.Queue()
|
q = queue.Queue()
|
||||||
|
|
||||||
|
VAD_FRAME_MS = 30
|
||||||
|
VAD_FRAME_BYTES = int(samplerate * VAD_FRAME_MS / 1000) * 2
|
||||||
|
vad = webrtcvad.Vad(config.VAD_AGGRESSIVENESS)
|
||||||
|
|
||||||
|
|
||||||
def gpt_answer():
|
def gpt_answer():
|
||||||
global message_log
|
global message_log
|
||||||
|
|
@ -175,8 +180,7 @@ def filter_cmd(raw_voice: str):
|
||||||
def recognize_cmd(cmd: str):
|
def recognize_cmd(cmd: str):
|
||||||
rc = {'cmd': '', 'percent': 0}
|
rc = {'cmd': '', 'percent': 0}
|
||||||
for c, v in VA_CMD_LIST.items():
|
for c, v in VA_CMD_LIST.items():
|
||||||
|
for x in v['phrases']:
|
||||||
for x in v:
|
|
||||||
vrt = fuzz.ratio(cmd, x)
|
vrt = fuzz.ratio(cmd, x)
|
||||||
if vrt > rc['percent']:
|
if vrt > rc['percent']:
|
||||||
rc['cmd'] = c
|
rc['cmd'] = c
|
||||||
|
|
@ -185,90 +189,59 @@ def recognize_cmd(cmd: str):
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
|
||||||
|
def _set_mute(state: int):
|
||||||
|
devices = AudioUtilities.GetSpeakers()
|
||||||
|
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
|
||||||
|
volume = cast(interface, POINTER(IAudioEndpointVolume))
|
||||||
|
volume.SetMute(state, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _shutdown():
|
||||||
|
try:
|
||||||
|
recorder.stop()
|
||||||
|
recorder.delete()
|
||||||
|
finally:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def run_action(action):
|
||||||
|
t = action['type']
|
||||||
|
if t == 'exe':
|
||||||
|
path = f"{CDIR}\\custom-commands\\{action['file']}"
|
||||||
|
if action.get('wait'):
|
||||||
|
subprocess.check_call([path])
|
||||||
|
else:
|
||||||
|
subprocess.Popen([path])
|
||||||
|
if 'delay_ms' in action:
|
||||||
|
time.sleep(action['delay_ms'] / 1000)
|
||||||
|
elif t == 'play_sound':
|
||||||
|
play(action['name'])
|
||||||
|
elif t == 'system':
|
||||||
|
op = action['op']
|
||||||
|
if op == 'volume_mute':
|
||||||
|
_set_mute(1)
|
||||||
|
elif op == 'volume_unmute':
|
||||||
|
_set_mute(0)
|
||||||
|
elif op == 'exit':
|
||||||
|
_shutdown()
|
||||||
|
elif t == 'sleep':
|
||||||
|
time.sleep(action['ms'] / 1000)
|
||||||
|
elif t == 'multi':
|
||||||
|
for step in action['steps']:
|
||||||
|
run_action(step)
|
||||||
|
|
||||||
|
|
||||||
def execute_cmd(cmd: str, voice: str):
|
def execute_cmd(cmd: str, voice: str):
|
||||||
if cmd == 'open_browser':
|
spec = VA_CMD_LIST.get(cmd)
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Run browser.exe'])
|
if not spec:
|
||||||
play("ok")
|
return
|
||||||
|
action = spec['action']
|
||||||
elif cmd == 'open_youtube':
|
run_action(action)
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Run youtube.exe'])
|
confirm = spec.get('confirm_sound')
|
||||||
play("ok")
|
if confirm is None and action['type'] == 'exe':
|
||||||
|
confirm = 'ok'
|
||||||
elif cmd == 'open_google':
|
if confirm:
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Run google.exe'])
|
play(confirm)
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'music':
|
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Run music.exe'])
|
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'music_off':
|
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Stop music.exe'])
|
|
||||||
time.sleep(0.2)
|
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'music_save':
|
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Save music.exe'])
|
|
||||||
time.sleep(0.2)
|
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'music_next':
|
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Next music.exe'])
|
|
||||||
time.sleep(0.2)
|
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'music_prev':
|
|
||||||
subprocess.Popen([f'{CDIR}\\custom-commands\\Prev music.exe'])
|
|
||||||
time.sleep(0.2)
|
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'sound_off':
|
|
||||||
play("ok", True)
|
|
||||||
|
|
||||||
devices = AudioUtilities.GetSpeakers()
|
|
||||||
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
|
|
||||||
volume = cast(interface, POINTER(IAudioEndpointVolume))
|
|
||||||
volume.SetMute(1, None)
|
|
||||||
|
|
||||||
elif cmd == 'sound_on':
|
|
||||||
devices = AudioUtilities.GetSpeakers()
|
|
||||||
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
|
|
||||||
volume = cast(interface, POINTER(IAudioEndpointVolume))
|
|
||||||
volume.SetMute(0, None)
|
|
||||||
|
|
||||||
play("ok")
|
|
||||||
|
|
||||||
elif cmd == 'thanks':
|
|
||||||
play("thanks")
|
|
||||||
|
|
||||||
elif cmd == 'stupid':
|
|
||||||
play("stupid")
|
|
||||||
|
|
||||||
elif cmd == 'gaming_mode_on':
|
|
||||||
play("ok")
|
|
||||||
subprocess.check_call([f'{CDIR}\\custom-commands\\Switch to gaming mode.exe'])
|
|
||||||
play("ready")
|
|
||||||
|
|
||||||
elif cmd == 'gaming_mode_off':
|
|
||||||
play("ok")
|
|
||||||
subprocess.check_call([f'{CDIR}\\custom-commands\\Switch back to workspace.exe'])
|
|
||||||
play("ready")
|
|
||||||
|
|
||||||
elif cmd == 'switch_to_headphones':
|
|
||||||
play("ok")
|
|
||||||
subprocess.check_call([f'{CDIR}\\custom-commands\\Switch to headphones.exe'])
|
|
||||||
time.sleep(0.5)
|
|
||||||
play("ready")
|
|
||||||
|
|
||||||
elif cmd == 'switch_to_dynamics':
|
|
||||||
play("ok")
|
|
||||||
subprocess.check_call([f'{CDIR}\\custom-commands\\Switch to dynamics.exe'])
|
|
||||||
time.sleep(0.5)
|
|
||||||
play("ready")
|
|
||||||
|
|
||||||
elif cmd == 'off':
|
|
||||||
play("off", True)
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=512)
|
recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=512)
|
||||||
|
|
@ -301,20 +274,43 @@ while True:
|
||||||
|
|
||||||
play("greet", True)
|
play("greet", True)
|
||||||
print("Yes, sir.")
|
print("Yes, sir.")
|
||||||
# reset the command recognizer so stale audio doesn't bleed into it
|
|
||||||
kaldi_rec.Reset()
|
kaldi_rec.Reset()
|
||||||
ltc = time.time()
|
listen_started = time.time()
|
||||||
|
speech_ms = 0
|
||||||
|
silence_ms = 0
|
||||||
|
vad_buf = b""
|
||||||
|
finalize = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
elapsed_ms = (time.time() - listen_started) * 1000
|
||||||
|
if elapsed_ms >= config.COMMAND_MAX_LISTEN_MS:
|
||||||
|
break
|
||||||
|
|
||||||
while time.time() - ltc <= 10:
|
|
||||||
pcm = recorder.read()
|
pcm = recorder.read()
|
||||||
sp = struct.pack("h" * len(pcm), *pcm)
|
sp = struct.pack("h" * len(pcm), *pcm)
|
||||||
|
kaldi_rec.AcceptWaveform(sp)
|
||||||
|
|
||||||
if kaldi_rec.AcceptWaveform(sp):
|
vad_buf += sp
|
||||||
if va_respond(json.loads(kaldi_rec.Result())["text"]):
|
while len(vad_buf) >= VAD_FRAME_BYTES:
|
||||||
ltc = time.time()
|
frame = vad_buf[:VAD_FRAME_BYTES]
|
||||||
|
vad_buf = vad_buf[VAD_FRAME_BYTES:]
|
||||||
|
if vad.is_speech(frame, samplerate):
|
||||||
|
speech_ms += VAD_FRAME_MS
|
||||||
|
silence_ms = 0
|
||||||
|
else:
|
||||||
|
silence_ms += VAD_FRAME_MS
|
||||||
|
|
||||||
|
if elapsed_ms < config.COMMAND_MIN_LISTEN_MS:
|
||||||
|
continue
|
||||||
|
if speech_ms >= config.COMMAND_MIN_SPEECH_MS and silence_ms >= config.COMMAND_END_SILENCE_MS:
|
||||||
|
finalize = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if finalize or speech_ms > 0:
|
||||||
|
text = json.loads(kaldi_rec.FinalResult()).get("text", "")
|
||||||
|
if text:
|
||||||
|
va_respond(text)
|
||||||
|
|
||||||
wake_rec.Reset()
|
wake_rec.Reset()
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ pvrecorder
|
||||||
# Vosk
|
# Vosk
|
||||||
vosk
|
vosk
|
||||||
|
|
||||||
|
# Voice activity detection (smart command-end)
|
||||||
|
webrtcvad-wheels
|
||||||
|
|
||||||
# Groq / OpenAI-compatible LLM
|
# Groq / OpenAI-compatible LLM
|
||||||
openai>=1.0
|
openai>=1.0
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue