language detection fixes and installer change.

This commit is contained in:
Khyretos 2024-01-03 14:26:08 +01:00
parent 5cfdd498c9
commit d83c0ad753
28 changed files with 216 additions and 149 deletions

4
.gitignore vendored
View file

@ -104,5 +104,7 @@ src/sounds/tts/*
loquendoBot_backend.spec loquendoBot_backend.spec
forge.config.js forge.config.js
backend/* backend/*
src/backend/loquendoBot_backend.exe !backend/loquendoBot_backend.py
backend/loquendoBot_backend.exe
src/config/twitch-emotes.json src/config/twitch-emotes.json
dist/*

View file

@ -11,7 +11,7 @@ logger = logging.getLogger("waitress")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
gevent.monkey.patch_all() gevent.monkey.patch_all()
import gevent.queue # import gevent.queue
import configparser import configparser
import pyttsx3 import pyttsx3
@ -28,8 +28,6 @@ from deep_translator import (
MyMemoryTranslator, MyMemoryTranslator,
) )
import emoji
from vosk import Model, KaldiRecognizer, SetLogLevel from vosk import Model, KaldiRecognizer, SetLogLevel
# global variables # global variables
@ -68,7 +66,7 @@ class LanguageDetection:
self.model = fasttext.load_model(language_detection_model) self.model = fasttext.load_model(language_detection_model)
def predict_lang(self, text): def predict_lang(self, text):
predictions = self.model.predict(text, k=5) # returns top 2 matching languages predictions = self.model.predict(text, k=3) # returns top 2 matching languages
language_codes = [] language_codes = []
for prediction in predictions[0]: for prediction in predictions[0]:
language_codes.append(prediction.replace("__label__", "")) language_codes.append(prediction.replace("__label__", ""))
@ -98,6 +96,7 @@ class STT:
vosk_model = os.path.join( vosk_model = os.path.join(
resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"] resources_folder, "speech_to_text_models", settings["STT"]["LANGUAGE"]
) )
print(vosk_model)
self.model = Model(rf"{vosk_model}") self.model = Model(rf"{vosk_model}")
self.dump_fn = None self.dump_fn = None
@ -137,8 +136,10 @@ class STT:
def stop_recognition(self): def stop_recognition(self):
self.is_running = False self.is_running = False
settings.read(settingsPath)
speech_recognition_service = STT() print(settingsPath)
if settings["STT"]["USE_STT"] and bool(settings["STT"]["LANGUAGE"]):
speech_recognition_service = STT()
class TTS: class TTS:
@ -156,16 +157,16 @@ class TTS:
break break
self.engine.setProperty("voice", matching_id) self.engine.setProperty("voice", matching_id)
settings_folder = os.path.dirname(settingsPath)
if environment == "dev": if environment == "dev":
settings_folder = os.path.dirname(settingsPath)
src_folder = os.path.dirname(settings_folder) src_folder = os.path.dirname(settings_folder)
bot_folder = os.path.dirname(src_folder)
saveLocation = os.path.join( saveLocation = os.path.join(
src_folder, "sounds\\tts", f"Internal_{count}.mp3" bot_folder, "sounds", f"Internal_{count}.mp3"
) )
else: else:
resources_folder = os.path.dirname(settingsPath)
saveLocation = os.path.join( saveLocation = os.path.join(
resources_folder, "sounds\\tts", f"Internal_{count}.mp3" settings_folder, "sounds", f"Internal_{count}.mp3"
) )
self.engine.save_to_file(message, saveLocation) self.engine.save_to_file(message, saveLocation)
@ -180,8 +181,9 @@ class TTS:
return [voice.name for voice in voices] return [voice.name for voice in voices]
settings.read(settingsPath)
text_to_speech_service = TTS() if settings["TTS"]["USE_TTS"]:
text_to_speech_service = TTS()
# endpoints # endpoints

View file

@ -1,36 +0,0 @@
module.exports = {
packagerConfig: {
icon: './src/images/icon.ico',
asar: true,
extraResource: ['./src/config/loquendo.db', './src/sounds', './backend', './language_detection_model', './speech_to_text_models'],
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
setupIcon: './src/images/icon.ico',
},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {
options: {},
},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
],
};

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Khyretis Copyright (c) 2021 Khyretos
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View file

@ -1,14 +1,37 @@
{ {
"name": "loquendo-bot", "name": "loquendo-bot",
"productName": "LoquendoBot",
"version": "2.5.0", "version": "2.5.0",
"description": "Bot assistant for streamers over different platforms", "description": "Bot assistant for streamers over different platforms",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
"start": "electron-forge start", "start": "electron-forge start",
"package": "npm run backend && electron-forge package", "build": "npm run backend && electron-builder",
"make": "electron-forge make",
"publish": "electron-forge publish", "publish": "electron-forge publish",
"backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./src/backend/loquendoBot_backend.py" "backend": "pyinstaller --noconsole --onefile --collect-all vosk --distpath ./backend ./backend/loquendoBot_backend.py"
},
"build": {
"appId": "LoquendoBot",
"win": {
"target": [
"nsis"
],
"icon": "./src/images/icon.ico"
},
"nsis": {
"oneClick": false,
"installerIcon": "./src/images/icon.ico",
"uninstallerIcon": "./src/images/icon.ico",
"uninstallDisplayName": "LoquendoBot-uninstaller",
"license": "license.md",
"allowToChangeInstallationDirectory": "true"
},
"extraResources": [
"speech_to_text_models/Where to get STT models.txt",
"backend/loquendoBot_backend.exe",
"language_detection_model",
"sounds"
]
}, },
"keywords": [], "keywords": [],
"author": { "author": {
@ -18,7 +41,6 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.4.0",
"electron-squirrel-startup": "^1.0.0",
"emoji-picker-element": "^1.21.0", "emoji-picker-element": "^1.21.0",
"express": "^4.18.2", "express": "^4.18.2",
"flag-icons": "^7.1.0", "flag-icons": "^7.1.0",
@ -38,12 +60,12 @@
"@electron-forge/cli": "^6.2.1", "@electron-forge/cli": "^6.2.1",
"@electron-forge/maker-deb": "^6.2.1", "@electron-forge/maker-deb": "^6.2.1",
"@electron-forge/maker-rpm": "^6.2.1", "@electron-forge/maker-rpm": "^6.2.1",
"@electron-forge/maker-squirrel": "^6.2.1",
"@electron-forge/maker-zip": "^6.2.1", "@electron-forge/maker-zip": "^6.2.1",
"@electron-forge/plugin-auto-unpack-natives": "^6.2.1", "@electron-forge/plugin-auto-unpack-natives": "^6.2.1",
"@electron-internal/eslint-config": "^1.0.1", "@electron-internal/eslint-config": "^1.0.1",
"@electron-toolkit/eslint-config": "^1.0.2", "@electron-toolkit/eslint-config": "^1.0.2",
"electron": "^25.9.8", "electron": "^25.9.8",
"electron-builder": "^24.9.1",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.2", "eslint-plugin-prettier": "^5.1.2",

View file

@ -1 +1,3 @@
https://alphacephei.com/vosk/models Download the model from here: https://alphacephei.com/vosk/models unzip it
and drop the folder in the 'speech_to_text_models' folder. Restart the app
to load the changes.

View file

@ -36,25 +36,14 @@ html {
box-sizing: inherit; box-sizing: inherit;
} }
html,
body { body {
height: 100%; height: 100%;
margin: 0; margin: 0;
/* border-top-left-radius: 20px; */
/* border-top-right-radius: 20px; */
overflow-x: hidden;
}
body {
font-family: 'Segoe UI', sans-serif; font-family: 'Segoe UI', sans-serif;
background: transparent; background: transparent;
} position: relative;
/* overflow-y: hidden;
/* Styling of window frame and titlebar */ overflow-x: hidden; */
body {
/* border: 1px solid #48545c; */
overflow-y: hidden;
} }
#titlebar { #titlebar {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -212,6 +212,11 @@
<div class="AdvancedMenuRow inputTTS"> <div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">2<sup>nd</sup> Internal Voice</div> <div class="AdvancedMenuLabel">2<sup>nd</sup> Internal Voice</div>
<select class="menu-select" name="secondaryVoice" id="secondaryVoice"></select> <select class="menu-select" name="secondaryVoice" id="secondaryVoice"></select>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_SECONDARY_TTS"
tip="This will only work if Language detection is enabled and a language for this voice has been selected"
></i>
</div> </div>
<div class="AdvancedMenuRow inputTTS"> <div class="AdvancedMenuRow inputTTS">
<div class="AdvancedMenuLabel">Test 2<sup>nd</sup> Internal Voice</div> <div class="AdvancedMenuLabel">Test 2<sup>nd</sup> Internal Voice</div>
@ -235,8 +240,13 @@
<select class="menu-select" name="microphone" id="microphone"></select> <select class="menu-select" name="microphone" id="microphone"></select>
</div> </div>
<div class="AdvancedMenuRow voiceLanguageDetection inputSTT"> <div class="AdvancedMenuRow voiceLanguageDetection inputSTT">
<div class="AdvancedMenuLabel">Voice Language</div> <div class="AdvancedMenuLabel">Voice Language model</div>
<select class="menu-select" name="sttModel" id="sttModel" tip="Language Service to use"></select> <select class="menu-select" name="sttModel" id="sttModel" tip="Language Service to use"></select>
<i
class="fa-solid fa-folder-open fa-2x SmallButton option-icon-container"
id="Info_VOICE_MODELS_FOLDER"
tip="Open Voice models folder"
></i>
</div> </div>
</fieldset> </fieldset>
@ -247,27 +257,42 @@
<label for="USE_DETECTION" class="toggle-small"></label> <label for="USE_DETECTION" class="toggle-small"></label>
<div class="AdvancedMenuLabel3">Enable Language detection</div> <div class="AdvancedMenuLabel3">Enable Language detection</div>
</legend> </legend>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Translate chat messages to</div>
<select class="menu-select" name="language" id="TRANSLATE_TO" tip="Language Service to use"></select>
</div>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Broadcast translation to chat</div>
<input type="checkbox" id="BROADCAST_TRANSLATION" class="checkbox" />
<label for="BROADCAST_TRANSLATION" class="toggle-small"></label>
</div>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Output translation to TTS</div>
<input type="checkbox" id="OUTPUT_TO_TTS" class="checkbox" />
<label for="OUTPUT_TO_TTS" class="toggle-small"></label>
</div>
<div class="AdvancedMenuRow outputToTtsInput"> <div class="AdvancedMenuRow outputToTtsInput">
<div class="AdvancedMenuLabel">Default TTS service language</div> <div class="AdvancedMenuLabel">Default TTS service language</div>
<select class="menu-select" name="defaultLanguage" id="defaultLanguage" tip="Language Service to use"></select> <select class="menu-select" name="defaultLanguage" id="defaultLanguage" tip="Language Service to use"></select>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_PRIMARY_TTS_LANGUAGE"
tip="When the selected language is detected Your Primary TTS voice will sound"
></i>
</div> </div>
<div class="AdvancedMenuRow outputToTtsInput"> <div class="AdvancedMenuRow outputToTtsInput">
<div class="AdvancedMenuLabel">2<sup>nd</sup> TTS service language</div> <div class="AdvancedMenuLabel">2<sup>nd</sup> TTS service language</div>
<select class="menu-select" name="secondaryLanguage" id="secondaryLanguage" tip="Language Service to use"></select> <select class="menu-select" name="secondaryLanguage" id="secondaryLanguage" tip="Language Service to use"></select>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_SECONDARY_TTS_LANGUAGE"
tip="When the selected language is detected Your Secondary TTS voice will sound"
></i>
</div>
<div class="AdvancedMenuRow languageDetectionInput">
<div class="AdvancedMenuLabel">Translate chat messages to</div>
<select class="menu-select" name="language" id="TRANSLATE_TO" tip="Language Service to use"></select>
</div>
<div class="AdvancedMenuRow languageDetectionInput TRANSLATE_TO">
<div class="AdvancedMenuLabel">Broadcast translation to chat</div>
<input type="checkbox" id="BROADCAST_TRANSLATION" class="checkbox TRANSLATE_TO" />
<label for="BROADCAST_TRANSLATION" class="toggle-small"></label>
</div>
<div class="AdvancedMenuRow languageDetectionInput TRANSLATE_TO">
<div class="AdvancedMenuLabel">Output translation to TTS</div>
<input type="checkbox" id="OUTPUT_TO_TTS" class="checkbox TRANSLATE_TO" />
<label for="OUTPUT_TO_TTS" class="toggle-small"></label>
<i
class="fa fa-question-circle fa-2x SmallButton option-icon-container"
id="Info_OUTPUT_TO_TTS"
tip="All translated messages will be send to primary TTS voice but if message is detected in Secondary TTS language it will output it to the Secondary TTS voice"
></i>
</div> </div>
</fieldset> </fieldset>

View file

@ -1,4 +1,4 @@
/* global settings, sound, twitch, getLanguageProperties, addSingleTooltip, showChatMessage, languageObject, addVoiceService, internalVoices, ttsRequestCount, main, path, pythonPath, settingsPath, ipcRenderer */ /* global settings, resourcesPath, sound, twitch, getLanguageProperties, addSingleTooltip, showChatMessage, languageObject, addVoiceService, internalVoices, ttsRequestCount, main, path, pythonPath, settingsPath, ipcRenderer */
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const kill = require('kill-process-by-name'); const kill = require('kill-process-by-name');
@ -45,35 +45,39 @@ async function getInstalledVoices() {
} }
function setTranslatedMessage(message) { function setTranslatedMessage(message) {
const messageBox = document.getElementById(message.messageId).getElementsByClassName('msg-box')[0]; if (message.language.selectedLanguage.ISO639 !== message.language.detectedLanguage.ISO639) {
const messageBox = document.getElementById(message.messageId).getElementsByClassName('msg-box')[0];
const translationHeader = document.createElement('div'); const translationHeader = document.createElement('div');
translationHeader.className = 'translation-header'; translationHeader.className = 'translation-header';
translationHeader.innerText = 'Translation'; translationHeader.innerText = 'Translation';
messageBox.appendChild(translationHeader); messageBox.appendChild(translationHeader);
const translationIcon = document.createElement('div'); const translationIcon = document.createElement('div');
translationIcon.className = 'translation-icon'; translationIcon.className = 'translation-icon';
const languageElement = document.createElement('span'); const languageElement = document.createElement('span');
const language = getLanguageProperties(settings.LANGUAGE.TRANSLATE_TO); const language = getLanguageProperties(settings.LANGUAGE.TRANSLATE_TO);
languageElement.classList = `fi fi-${language.ISO3166} fis`; languageElement.classList = `fi fi-${message.language.selectedLanguage.ISO3166} fis`;
languageElement.setAttribute('tip', language.name); languageElement.setAttribute('tip', message.language.selectedLanguage.name);
addSingleTooltip(languageElement); addSingleTooltip(languageElement);
translationIcon.appendChild(languageElement); translationIcon.appendChild(languageElement);
messageBox.appendChild(translationIcon); messageBox.appendChild(translationIcon);
const translationMessage = document.createElement('div');
translationMessage.className = 'translation-message';
translationMessage.innerText = message.translation;
messageBox.appendChild(translationMessage);
showChatMessage();
}
const translationMessage = document.createElement('div');
translationMessage.className = 'translation-message';
translationMessage.innerText = message.translation;
messageBox.appendChild(translationMessage);
showChatMessage();
if (settings.LANGUAGE.OUTPUT_TO_TTS) { if (settings.LANGUAGE.OUTPUT_TO_TTS) {
sound.playVoice({ sound.playVoice({
originalMessage: message.originalMessage,
filteredMessage: message.translation, filteredMessage: message.translation,
logoUrl: message.logoUrl, logoUrl: message.logoUrl,
username: message.username, username: message.username,
formattedMessage: message.formattedMessage, formattedMessage: message.formattedMessage,
language language: message.language
}); });
} }
} }
@ -86,7 +90,7 @@ async function getTranslatedMessage(message) {
}, },
body: JSON.stringify({ body: JSON.stringify({
message: message.message, message: message.message,
language: message.language language: message.language.detectedLanguage.IETF
}) // Convert the data to JSON and include it in the request body }) // Convert the data to JSON and include it in the request body
}; };
@ -97,15 +101,18 @@ async function getTranslatedMessage(message) {
console.log('Translated message:', responseData); console.log('Translated message:', responseData);
setTranslatedMessage({ setTranslatedMessage({
originalMessage: message.message,
translation: responseData.translation, translation: responseData.translation,
messageId: message.messageId, messageId: message.messageId,
ISO3166: message.ISO3166, language: message.language,
formattedMessage: message.formattedMessage, formattedMessage: message.formattedMessage,
username: message.username, username: message.username,
logoUrl: message.logoUrl logoUrl: message.logoUrl
}); });
if (settings.LANGUAGE.BROADCAST_TRANSLATION) { if (settings.LANGUAGE.BROADCAST_TRANSLATION) {
twitch.sendMessage(`[${message.language} > ${settings.LANGUAGE.TRANSLATE_TO}] @${message.username}: ${responseData.translation}`); twitch.sendMessage(
`[${message.language.detectedLanguage.name} ${message.language.detectedLanguage.ISO639} > ${message.language.selectedLanguage.name} ${message.language.selectedLanguage.ISO639}] @${message.username}: ${responseData.translation}`
);
} }
} else { } else {
console.error('Failed to send termination signal to Flask server.'); console.error('Failed to send termination signal to Flask server.');
@ -119,19 +126,40 @@ async function getTranslatedMessage(message) {
} }
function filterLanguage(message) { function filterLanguage(message) {
const language = getLanguageProperties(message.language); const selectedPrimaryLanguage = getLanguageProperties(settings.LANGUAGE.TRANSLATE_TO);
const selectedPrimaryLanguageIndex =
if (settings.LANGUAGE.TRANSLATE_TO !== 'none') { message.languages.indexOf(selectedPrimaryLanguage.ISO639) === -1 ? 99 : message.languages.indexOf(selectedPrimaryLanguage.ISO639);
const selectedSecondaryLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE);
const selectedSecondaryLanguageIndex =
message.languages.indexOf(selectedSecondaryLanguage.ISO639) === -1 ? 99 : message.languages.indexOf(selectedSecondaryLanguage.ISO639);
const detectedLanguage = getLanguageProperties(message.languages[0]);
const language = selectedPrimaryLanguageIndex < selectedSecondaryLanguageIndex ? selectedPrimaryLanguage : detectedLanguage;
if (settings.LANGUAGE.TRANSLATE_TO !== 'none' && selectedPrimaryLanguage.ISO639 !== detectedLanguage.ISO639) {
getTranslatedMessage({ getTranslatedMessage({
message: message.message, message: message.message,
messageId: message.messageId, messageId: message.messageId,
language: language.IETF, language: {
ISO3166: language.ISO3166, selectedLanguage: selectedPrimaryLanguage,
detectedLanguage
},
username: message.username, username: message.username,
formattedMessage: message.formattedMessage formattedMessage: message.formattedMessage,
logoUrl: message.logoUrl
});
} else {
setTranslatedMessage({
originalMessage: message.message,
translation: message.message,
messageId: message.messageId,
language: {
selectedLanguage: selectedPrimaryLanguage,
detectedLanguage: selectedPrimaryLanguage
},
formattedMessage: message.formattedMessage,
username: message.username,
logoUrl: message.logoUrl
}); });
} }
return language; return language;
} }
@ -155,7 +183,7 @@ async function getDetectedLanguage(message) {
console.log('Detected Languages:', responseData); console.log('Detected Languages:', responseData);
return filterLanguage({ return filterLanguage({
language: responseData.languages[0], languages: responseData.languages,
message: message.message, message: message.message,
messageId: message.messageId, messageId: message.messageId,
username: message.username, username: message.username,
@ -232,7 +260,7 @@ const createBackendServer = () =>
if (main.isPackaged) { if (main.isPackaged) {
python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']); python = spawn(path.join(pythonPath, './loquendoBot_backend.exe'), [settingsPath, 'prod']);
} else { } else {
python = spawn('python', ['-u', path.join(pythonPath, './loquendoBot_backend.py'), settingsPath, 'dev']); python = spawn('python', ['-u', path.join(resourcesPath, '../backend/loquendoBot_backend.py'), settingsPath, 'dev']);
} }
// Capture the stdout of the Python process // Capture the stdout of the Python process
python.stdout.on('data', data => { python.stdout.on('data', data => {
@ -266,7 +294,7 @@ async function initiateBackend() {
createBackendServer().then(() => { createBackendServer().then(() => {
getBackendServerStatus(); getBackendServerStatus();
getInstalledVoices(); getInstalledVoices();
if (settings.STT.USE_STT) { if (settings.STT.USE_STT && !settings.STT.LANGUAGE === '') {
startSTT(); startSTT();
} }
}); });

View file

@ -73,6 +73,7 @@ const languages = {
'crimean tatar': { IETF: 'crh-RU', ISO639: 'crh', ISO3166: 'tr' }, 'crimean tatar': { IETF: 'crh-RU', ISO639: 'crh', ISO3166: 'tr' },
'crioulo upper guinea': { IETF: 'pov-GW', ISO639: 'pov', ISO3166: 'gw' }, 'crioulo upper guinea': { IETF: 'pov-GW', ISO639: 'pov', ISO3166: 'gw' },
croatian: { IETF: 'hr-HR', ISO639: 'hr', ISO3166: 'hr' }, croatian: { IETF: 'hr-HR', ISO639: 'hr', ISO3166: 'hr' },
'serbo-croatian': { IETF: 'sr-Cyrl-RS', ISO639: 'sh', ISO3166: 'sr' },
czech: { IETF: 'cs-CZ', ISO639: 'cs', ISO3166: 'cz' }, czech: { IETF: 'cs-CZ', ISO639: 'cs', ISO3166: 'cz' },
danish: { IETF: 'da-DK', ISO639: 'da', ISO3166: 'dk' }, danish: { IETF: 'da-DK', ISO639: 'da', ISO3166: 'dk' },
dari: { IETF: 'prs-AF', ISO639: 'prs', ISO3166: 'af' }, dari: { IETF: 'prs-AF', ISO639: 'prs', ISO3166: 'af' },
@ -108,6 +109,7 @@ const languages = {
garo: { IETF: 'grt-IN', ISO639: 'grt', ISO3166: 'in' }, garo: { IETF: 'grt-IN', ISO639: 'grt', ISO3166: 'in' },
georgian: { IETF: 'ka-GE', ISO639: 'ka', ISO3166: 'ge' }, georgian: { IETF: 'ka-GE', ISO639: 'ka', ISO3166: 'ge' },
german: { IETF: 'de-DE', ISO639: 'de', ISO3166: 'de' }, german: { IETF: 'de-DE', ISO639: 'de', ISO3166: 'de' },
'Low German': { IETF: 'nl-NL', ISO639: 'nds', ISO3166: 'nl' },
gilbertese: { IETF: 'gil-KI', ISO639: 'gil', ISO3166: 'ki' }, gilbertese: { IETF: 'gil-KI', ISO639: 'gil', ISO3166: 'ki' },
glavda: { IETF: 'glw-NG', ISO639: 'glw', ISO3166: 'ng' }, glavda: { IETF: 'glw-NG', ISO639: 'glw', ISO3166: 'ng' },
greek: { IETF: 'el-GR', ISO639: 'el', ISO3166: 'gr' }, greek: { IETF: 'el-GR', ISO639: 'el', ISO3166: 'gr' },

View file

@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
const fs = require('fs'); const fs = require('fs');
const ini = require('ini'); const ini = require('ini');
const path = require('path'); // get directory path const path = require('path'); // get directory path
@ -49,8 +50,8 @@ const config = require(path.join(__dirname, './js/settings'));
const mediaDevices = require(path.join(__dirname, './js/mediaDevices')); const mediaDevices = require(path.join(__dirname, './js/mediaDevices'));
const notificationSounds = path.join(__dirname, './sounds/notifications'); const notificationSounds = path.join(resourcesPath, main.isPackaged ? './sounds/notifications' : '../sounds/notifications');
const sttModels = path.join(__dirname, '../speech_to_text_models'); const sttModels = path.join(resourcesPath, main.isPackaged ? './speech_to_text_models' : '../speech_to_text_models');
function reset() { function reset() {
ipcRenderer.send('restart'); ipcRenderer.send('restart');
@ -233,10 +234,10 @@ function showChatMessage(article) {
document.querySelector('#chatBox').appendChild(article); document.querySelector('#chatBox').appendChild(article);
} }
const messages = Array.from(document.body.querySelectorAll('.msg-container')); const messages = document.body.querySelectorAll('.msg-container');
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
lastMessage.scrollIntoView({ behavior: 'smooth' }); lastMessage.scrollIntoView();
} }
function getPostTime() { function getPostTime() {

View file

@ -1,4 +1,4 @@
/* global settings, setZoomLevel, webFrame, theme, fs, settingsPath, ini, startVoiceRecognition,notificationSoundAudioDevices, ttsAudioDevices, notificationSound, path, resourcesPath, ipcRenderer, auth, shell, sound, twitch, server, backend */ /* global settings,main sttModels, setZoomLevel, webFrame, theme, fs, settingsPath, ini, startVoiceRecognition,notificationSoundAudioDevices, ttsAudioDevices, notificationSound, path, resourcesPath, ipcRenderer, auth, shell, sound, twitch, server, backend */
function getGeneralSettings() { function getGeneralSettings() {
// General // General
@ -130,10 +130,34 @@ document.body.querySelector('#language').addEventListener('change', () => {
createNotification('Saved language!', 'success'); createNotification('Saved language!', 'success');
}); });
function setTranslateToOptions() {
const options = document.querySelectorAll('.TRANSLATE_TO');
const index = parseInt(settings.LANGUAGE.TRANSLATE_TO_INDEX);
if (index === 0) {
settings.LANGUAGE.BROADCAST_TRANSLATION = false;
settings.LANGUAGE.OUTPUT_TO_TTS = false;
options.forEach(item => {
item.style.visibility = 'hidden';
item.style.height = '0px';
item.checked = false;
});
} else {
options.forEach(item => {
item.style.visibility = '';
item.style.height = '';
});
}
}
setTranslateToOptions();
document.body.querySelector('#TRANSLATE_TO').addEventListener('change', () => { document.body.querySelector('#TRANSLATE_TO').addEventListener('change', () => {
const select = document.querySelector('#TRANSLATE_TO'); const select = document.querySelector('#TRANSLATE_TO');
settings.LANGUAGE.TRANSLATE_TO_INDEX = select.selectedIndex; settings.LANGUAGE.TRANSLATE_TO_INDEX = select.selectedIndex;
settings.LANGUAGE.TRANSLATE_TO = select.options[select.selectedIndex].value; settings.LANGUAGE.TRANSLATE_TO = select.options[select.selectedIndex].value;
setTranslateToOptions();
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification('Saved primary voice!', 'success'); createNotification('Saved primary voice!', 'success');
}); });
@ -227,7 +251,9 @@ function createNotification(message = null, type = null) {
} }
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
const notfication = new Audio(path.join(resourcesPath, `./sounds/notifications/${alertSound}`)); const notfication = new Audio(
path.join(resourcesPath, main.isPackaged ? `./sounds/notifications/${alertSound}` : `../sounds/notifications/${alertSound}`)
);
notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100; notfication.volume = settings.AUDIO.NOTIFICATION_VOLUME / 100;
notfication.play(); notfication.play();
} }
@ -268,6 +294,10 @@ document.body.querySelector('#OPEN_SETTINGS_FILE').addEventListener('click', ()
shell.openExternal(settingsPath); shell.openExternal(settingsPath);
}); });
document.body.querySelector('#Info_VOICE_MODELS_FOLDER').addEventListener('click', () => {
shell.openExternal(sttModels);
});
// #region Use Custom theme toggle logic // #region Use Custom theme toggle logic
document.body.querySelector('#USE_CUSTOM_THEME').addEventListener('click', () => { document.body.querySelector('#USE_CUSTOM_THEME').addEventListener('click', () => {
const toggle = document.getElementById('USE_CUSTOM_THEME').checked; const toggle = document.getElementById('USE_CUSTOM_THEME').checked;
@ -488,14 +518,6 @@ document.body.querySelector('#USE_STT').addEventListener('change', () => {
createNotification(`${toggle ? 'Enabled' : 'Disabled'} speech to text!`, 'success'); createNotification(`${toggle ? 'Enabled' : 'Disabled'} speech to text!`, 'success');
}); });
function toggleOutputToTts() {
const toggle = settings.LANGUAGE.OUTPUT_TO_TTS;
const inputs = document.getElementsByClassName('outputToTtsInput');
toggleRadio(toggle, inputs);
}
toggleOutputToTts();
document.body.querySelector('#OUTPUT_TO_TTS').addEventListener('change', () => { document.body.querySelector('#OUTPUT_TO_TTS').addEventListener('change', () => {
let toggle = document.getElementById('OUTPUT_TO_TTS').checked; let toggle = document.getElementById('OUTPUT_TO_TTS').checked;
if (!settings.TTS.USE_TTS) { if (!settings.TTS.USE_TTS) {
@ -505,8 +527,6 @@ document.body.querySelector('#OUTPUT_TO_TTS').addEventListener('change', () => {
} }
settings.LANGUAGE.OUTPUT_TO_TTS = toggle; settings.LANGUAGE.OUTPUT_TO_TTS = toggle;
const inputs = document.getElementsByClassName('outputToTtsInput');
toggleRadio(toggle, inputs);
fs.writeFileSync(settingsPath, ini.stringify(settings)); fs.writeFileSync(settingsPath, ini.stringify(settings));
createNotification(`${toggle ? 'Enabled' : 'Disabled'} Outputting translations to TTS!`, 'success'); createNotification(`${toggle ? 'Enabled' : 'Disabled'} Outputting translations to TTS!`, 'success');
}); });

View file

@ -1,4 +1,4 @@
/* global ttsAudioFile, path, getLanguageProperties, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */ /* global ttsAudioFile, main, path, getLanguageProperties, resourcesPath, settings, fs, notificationSound, backend, socket, requestData */
const voiceSoundArray = []; const voiceSoundArray = [];
let status = 0; let status = 0;
@ -6,7 +6,10 @@ const counter = 0;
const playTTS = data => const playTTS = data =>
new Promise(resolve => { new Promise(resolve => {
ttsAudioFile = path.join(resourcesPath, `./sounds/tts/${data.service}_${data.count}.mp3`); ttsAudioFile = path.join(
resourcesPath,
main.isPackaged ? `./sounds/${data.service}_${data.count}.mp3` : `../sounds/${data.service}_${data.count}.mp3`
);
const tts = new Audio(ttsAudioFile); const tts = new Audio(ttsAudioFile);
tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE); tts.setSinkId(settings.AUDIO.TTS_AUDIO_DEVICE);
@ -58,7 +61,12 @@ function add(data) {
function playNotificationSound() { function playNotificationSound() {
if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) { if (settings.AUDIO.USE_NOTIFICATION_SOUNDS) {
const notfication = new Audio( const notfication = new Audio(
path.join(resourcesPath, `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`) path.join(
resourcesPath,
main.isPackaged
? `./sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`
: `../sounds/notifications/${notificationSound.options[settings.AUDIO.NOTIFICATION_SOUND].text}`
)
); );
notfication notfication
@ -93,10 +101,11 @@ async function playVoice(message) {
let voice = settings.TTS.PRIMARY_VOICE; let voice = settings.TTS.PRIMARY_VOICE;
textObject.filtered = `${message.username}: ${message.filteredMessage}`; textObject.filtered = `${message.username}: ${message.filteredMessage}`;
if (settings.LANGUAGE.USE_DETECTION && settings.TTS.SECONDARY_VOICE && settings.LANGUAGE.OUTPUT_TO_TTS) { if (settings.LANGUAGE.USE_DETECTION && settings.TTS.SECONDARY_VOICE) {
const secondaryTTSLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE); const secondaryTTSLanguage = getLanguageProperties(settings.TTS.SECONDARY_TTS_LANGUAGE);
if (message.language.ISO639 === secondaryTTSLanguage.ISO639) { if (message.language.detectedLanguage === null || message.language.detectedLanguage.ISO639 === secondaryTTSLanguage.ISO639) {
voice = settings.TTS.SECONDARY_VOICE; voice = settings.TTS.SECONDARY_VOICE;
textObject.filtered = message.originalMessage ? message.originalMessage : message.filteredMessage;
} }
} }

View file

@ -97,6 +97,7 @@ function ping(element) {
} }
async function displayTwitchMessage(logoUrl, username, messageObject, filteredMessage) { async function displayTwitchMessage(logoUrl, username, messageObject, filteredMessage) {
messageId++;
const article = document.createElement('article'); const article = document.createElement('article');
article.className = 'msg-container sender'; article.className = 'msg-container sender';
article.setAttribute('id', messageId); article.setAttribute('id', messageId);
@ -144,7 +145,13 @@ async function displayTwitchMessage(logoUrl, username, messageObject, filteredMe
showChatMessage(article); showChatMessage(article);
if (filteredMessage && !settings.LANGUAGE.OUTPUT_TO_TTS) { if (filteredMessage && !settings.LANGUAGE.OUTPUT_TO_TTS) {
sound.playVoice({ filteredMessage, logoUrl, username, formattedMessage, language }); sound.playVoice({
filteredMessage,
logoUrl,
username,
formattedMessage,
language: { selectedLanguage: null, detectedLanguage: language }
});
} }
window.article = article; window.article = article;
@ -159,7 +166,6 @@ async function displayTwitchMessage(logoUrl, username, messageObject, filteredMe
window.article = article; window.article = article;
} }
messageId++;
sound.playNotificationSound(); sound.playNotificationSound();
} }

View file

@ -24,11 +24,6 @@ if (app.isPackaged) {
pythonPath = path.join(resourcesPath, './backend'); pythonPath = path.join(resourcesPath, './backend');
} }
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}
async function createWindow() { async function createWindow() {
if (!fs.existsSync(settingsPath)) { if (!fs.existsSync(settingsPath)) {
console.log(resourcesPath); console.log(resourcesPath);
@ -164,7 +159,7 @@ async function createIniFile() {
MICROPHONE_ID: 'default', MICROPHONE_ID: 'default',
SELECTED_MICROPHONE: 'default', SELECTED_MICROPHONE: 'default',
MICROPHONE: 0, MICROPHONE: 0,
LANGUAGE: 'vosk-model-small-es-0.42' LANGUAGE: ''
}, },
AUDIO: { AUDIO: {
USE_NOTIFICATION_SOUNDS: false, USE_NOTIFICATION_SOUNDS: false,

Binary file not shown.