Merge branch 'develop'

This commit is contained in:
misygauziya
2023-07-22 05:47:38 +09:00
14 changed files with 2408 additions and 1028 deletions

View File

@@ -24,11 +24,17 @@ pip install -r requirements.txt
```bash ```bash
git clone https://github.com/misyaguziya/translators.git git clone https://github.com/misyaguziya/translators.git
python ./translators/setup.py install cd translators
python ./setup.py install
cd ../
git clone https://github.com/misyaguziya/deepl-translate.git git clone https://github.com/misyaguziya/deepl-translate.git
python ./deepl_translate/setup.py install cd deepl-translate
python ./setup.py install
cd ../
git clone https://github.com/misyaguziya/custom_speech_recognition.git git clone https://github.com/misyaguziya/custom_speech_recognition.git
python ./custom_speech_recognition/setup.py install cd custom_speech_recognition
python ./setup.py install
cd ../
``` ```
## Usage ## Usage
@@ -44,7 +50,7 @@ ptyhon VRCT.py
(任意) (任意)
1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する
2. ギアアイコンのボタンでconfigウィンドウを開く 2. ギアアイコンのボタンでconfigウィンドウを開く
3. ParameterタブのDeepL Auth Keyに認証キーを記載し、フロッピーアイコンのボタンを押す 3. ParameterタブのDeepL Auth Keyに認証キーを記載
4. configウィンドウを閉じる 4. configウィンドウを閉じる
### Normal use ### Normal use
@@ -69,22 +75,29 @@ ptyhon VRCT.py
- Appearance Theme: ウィンドウテーマを選択 - Appearance Theme: ウィンドウテーマを選択
- UI Scaling: UIサイズを調整 - UI Scaling: UIサイズを調整
- Font Family: 表示フォントを選択 - Font Family: 表示フォントを選択
- **(New!) UI Language: UIの表示言語を選択**
- Translation tab - Translation tab
- Select Translator: 翻訳エンジンの変更 - Select Translator: 翻訳エンジンの変更
- Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択
- Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択
- Transcription tab - Transcription tab
- **(New!) Input Mic Host: マイクのホストAPIを選択**
- Input Mic Device: マイクを選択 - Input Mic Device: マイクを選択
- Input Mic Voice Language: 入力する音声の言語 - Input Mic Voice Language: 入力する音声の言語
- Input Mic Energy Threshold: 音声取得のしきい値 - Input Mic Energy Threshold: 音声取得のしきい値
- **(New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化**
- Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整
- Input Mic Phase Timeout: 文字起こしする音声時間の上限
- Input Mic Record Timeout: 音声の区切りの無音時間 - Input Mic Record Timeout: 音声の区切りの無音時間
- Input Mic Max Phrases: 保留する単語の上限 - Input Mic Max Phrases: 保留する単語の上限
- **(New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC)**
- Input Speaker Device: スピーカーを選択 - Input Speaker Device: スピーカーを選択
- Input Speaker Voice Language: 受信する音声の言語 - Input Speaker Voice Language: 受信する音声の言語
- Input Speaker Energy Threshold: 音声取得のしきい値 - Input Speaker Energy Threshold: 音声取得のしきい値
- **(New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化**
- Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整
- Input Speaker Record Timeout: 音声の区切りの無音時間 - Input Speaker Record Timeout: 音声の区切りの無音時間
- Input Speaker Phase Timeout: 文字起こしする音声時間の上限
- Input Speaker Max Phrases: 保留する単語の上限 - Input Speaker Max Phrases: 保留する単語の上限
- Parameter tab - Parameter tab
- OSC IP address: 変更不要 - OSC IP address: 変更不要
@@ -94,8 +107,12 @@ ptyhon VRCT.py
- [message]がメッセージボックスに記入したメッセージに置換される - [message]がメッセージボックスに記入したメッセージに置換される
- [translation]が翻訳されたメッセージに置換される - [translation]が翻訳されたメッセージに置換される
- 初期フォーマット:`[message]([translation])` - 初期フォーマット:`[message]([translation])`
- Others tab
- **(New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする**
## Author ## Author
みしゃ(misyaguzi) みしゃ(misyaguzi)
twitter: https://twitter.com/misya_ai twitter: https://twitter.com/misya_ai
booth: https://misyaguziya.booth.pm/items/4814313 booth: https://misyaguziya.booth.pm/items/4814313
Shiina_12siy

View File

@@ -1,77 +1,90 @@
ご購入ありがとうございます。 ご購入ありがとうございます。
フィードバックお待ちしております。 フィードバックお待ちしております。
概要 # 概要
VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。
翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。
(翻訳エンジンはDeepL,Google,Bingに対応) (翻訳エンジンはDeepL,Google,Bingに対応)
使用方法 # 使用方法
 初期設定時 初期設定時
  0. VRChatのOSCを有効にする重要 0. VRChatのOSCを有効にする重要
  (任意) (任意)
  1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する
  2. ギアアイコンのボタンでconfigウィンドウを開く 2. ギアアイコンのボタンでconfigウィンドウを開く
  3. ParameterタブのDeepL Auth Keyに認証キーを記載し、フロッピーアイコンのボタンを押す 3. ParameterタブのDeepL Auth Keyに認証キーを記載
  4. configウィンドウを閉じる 4. configウィンドウを閉じる
 通常使用時 通常使用時
  1. メッセージボックスにメッセージを記入 1. メッセージボックスにメッセージを記入
  2. Enterキーを押し、メッセージを送信する 2. Enterキーを押し、メッセージを送信する
 その他の設定 # その他の設定
  - translation チェックボックス: 翻訳の有効無効 translation チェックボックス: 翻訳の有効無効
  - voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する
  - speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する
  - foreground チェックボックス: 最前面表示の有効無効 foreground チェックボックス: 最前面表示の有効無効
   - テキストボックス テキストボックス
    - logタブ: すべてのログを表示 logタブ
    - sendタブ: 送信したメッセージを表示 すべてのログを表示
    - receiveタブ: 受信したメッセージを表示 sendタブ
    - systemタブ: 機能についてのメッセージを表示 送信したメッセージを表示
receiveタブ
受信したメッセージを表示
systemタブ
機能についてのメッセージを表示
  - Configウィンドウ configウィンドウ
   - UIタブ UIタブ
    - Transparency: ウィンドウの透過度の調整 Transparency: ウィンドウの透過度の調整
    - Appearance Theme: ウィンドウテーマを選択 Appearance Theme: ウィンドウテーマを選択
    - UI Scaling: UIサイズを調整 UI Scaling: UIサイズを調整
    - Font Family: 表示フォントを選択 Font Family: 表示フォントを選択
   - Translationタブ (New!) UI Language: UIの表示言語を選択
    - Select Translator: 翻訳エンジンの変更 Translationタブ
    - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 Select Translator: 翻訳エンジンの変更
    - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択
   - Transcriptionタブ Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択
    - Input Mic Device: マイクを選択 Transcriptionタブ
    - Input Mic Voice Language: 入力する音声の言語 (New!) Input Mic Host: マイクのホストAPIを選択
    - Input Mic Energy Threshold: 音声取得のしきい値 Input Mic Device: マイクを選択
    - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Mic Voice Language: 入力する音声の言語
    - Input Mic Record Timeout: 音声の区切りの無音時間 Input Mic Energy Threshold: 音声取得のしきい値
    - Input Mic Max Phrases: 保留する単語の上限 (New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化
    - Input Speaker Device: スピーカーを選択 Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整
    - Input Speaker Voice Language: 受信する音声の言語 Input Mic Record Timeout: 音声の区切りの無音時間
    - Input Speaker Energy Threshold: 音声取得のしきい値 Input Mic Phase Timeout: 文字起こしする音声時間の上限
    - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Mic Max Phrases: 保留する単語の上限
    - Input Speaker Record Timeout: 音声の区切りの無音時間 (New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC)
    - Input Speaker Max Phrases: 保留する単語の上限 Input Speaker Device: スピーカーを選択
  - Parameterタブ Input Speaker Voice Language: 受信する音声の言語
    - OSC IP address: 変更不要 Input Speaker Energy Threshold: 音声取得のしきい値
    - OSC port: 変更不要 (New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化
    - DeepL Auth key: DeepLの認証キーの設定 Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整
    - Message Format: 送信するメッセージのデコレーションの設定 Input Speaker Record Timeout: 音声の区切りの無音時間
     - [message]がメッセージボックスに記入したメッセージに置換される Input Speaker Phase Timeout: 文字起こしする音声時間の上限
     - [translation]が翻訳されたメッセージに置換される Input Speaker Max Phrases: 保留する単語の上限
     - 初期フォーマット:"[message]([translation])" Parameterタブ
OSC IP address: 変更不要
OSC port: 変更不要
DeepL Auth key: DeepLの認証キーの設定
Message Format: 送信するメッセージのデコレーションの設定
[message]がメッセージボックスに記入したメッセージに置換される
[translation]が翻訳されたメッセージに置換される
初期フォーマット:"[message]([translation])"
Othersタブ
(New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする
 設定の初期化 設定の初期化
  - config.jsonを削除 config.jsonを削除
お問い合わせ # お問い合わせ
要望などはTwitterまで 要望などはTwitterまで
https://twitter.com/misya_ai https://twitter.com/misya_ai
アップデート履歴 # アップデート履歴
[2023-05-29: v0.1b] v0.1b リリース [2023-05-29: v0.1b] v0.1b リリース
[2023-05-30: v0.2b] [2023-05-30: v0.2b]
- configボタンをギアアイコンに変更 - configボタンをギアアイコンに変更
@@ -97,6 +110,15 @@ https://twitter.com/misya_ai
- 文字起こしの処理の軽量化 - 文字起こしの処理の軽量化
[2023-07-05: v1.2] [2023-07-05: v1.2]
- 文字起こし精度の向上 - 文字起こし精度の向上
[2023-07-21: v1.3]
- UIの表示言語を日本語/英語を選択できる機能を追加
- Energy Thresholdの視覚化機能を追加
- 文字起こしの誤認識対策のため、Word Filterを追加
- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加
- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加
- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更
- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更
- 半角入力時に一部の文字が書き込めないバグを修正
# 注意事項 # 注意事項
再配布とかはやめてね 再配布とかはやめてね

652
VRCT.py
View File

@@ -1,29 +1,35 @@
import os from os import path as os_path
import json from json import load as json_load
import queue from json import dump as json_dump
from queue import Queue
import tkinter as tk import tkinter as tk
import customtkinter import customtkinter
from PIL import Image from customtkinter import CTk, CTkFrame, CTkCheckBox, CTkFont, CTkButton, CTkImage, CTkTabview, CTkTextbox, CTkEntry
from PIL.Image import open as Image_open
from flashtext import KeywordProcessor
import utils from threading import Thread
import osc_tools from utils import save_json, print_textbox, thread_fnc, get_localized_text, widget_main_window_label_setter
import window_config from osc_tools import send_typing, send_message
import window_information from window_config import ToplevelWindowConfig
import languages from window_information import ToplevelWindowInformation
import audio_utils from languages import transcription_lang, translators, translation_lang, selectable_languages
import audio_recorder from audio_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device
import audio_transcriber from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder
import translation from audio_transcriber import AudioTranscriber
from translation import Translator
class App(customtkinter.CTk): class App(CTk):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# init instance # init instance
self.translator = translation.Translator() self.translator = Translator()
self.keyword_processor = KeywordProcessor()
# init config # init config
self.PATH_CONFIG = "./config.json" self.PATH_CONFIG = "./config.json"
## main window ## main window
self.ENABLE_TRANSLATION = False self.ENABLE_TRANSLATION = False
self.ENABLE_TRANSCRIPTION_SEND = False self.ENABLE_TRANSCRIPTION_SEND = False
@@ -34,23 +40,26 @@ class App(customtkinter.CTk):
self.APPEARANCE_THEME = "System" self.APPEARANCE_THEME = "System"
self.UI_SCALING = "100%" self.UI_SCALING = "100%"
self.FONT_FAMILY = "Yu Gothic UI" self.FONT_FAMILY = "Yu Gothic UI"
self.UI_LANGUAGE = "en"
## Translation ## Translation
self.CHOICE_TRANSLATOR = languages.translators[0] self.CHOICE_TRANSLATOR = translators[0]
self.INPUT_SOURCE_LANG = list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys())[0] self.INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[0]
self.INPUT_TARGET_LANG = list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys())[1] self.INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[1]
self.OUTPUT_SOURCE_LANG = list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys())[1] self.OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[1]
self.OUTPUT_TARGET_LANG = list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys())[0] self.OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR].keys())[0]
## Transcription Send ## Transcription Send
self.CHOICE_MIC_DEVICE = audio_utils.get_default_input_device()["name"] self.CHOICE_MIC_HOST = get_default_input_device()["host"]["name"]
self.INPUT_MIC_VOICE_LANGUAGE = list(languages.transcription_lang.keys())[0] self.CHOICE_MIC_DEVICE = get_default_input_device()["device"]["name"]
self.INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0]
self.INPUT_MIC_ENERGY_THRESHOLD = 300 self.INPUT_MIC_ENERGY_THRESHOLD = 300
self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True
self.INPUT_MIC_RECORD_TIMEOUT = 3 self.INPUT_MIC_RECORD_TIMEOUT = 3
self.INPUT_MIC_PHRASE_TIMEOUT = 3 self.INPUT_MIC_PHRASE_TIMEOUT = 3
self.INPUT_MIC_MAX_PHRASES = 10 self.INPUT_MIC_MAX_PHRASES = 10
self.INPUT_MIC_WORD_FILTER = []
## Transcription Receive ## Transcription Receive
self.CHOICE_SPEAKER_DEVICE = audio_utils.get_default_output_device()["name"] self.CHOICE_SPEAKER_DEVICE = get_default_output_device()["name"]
self.INPUT_SPEAKER_VOICE_LANGUAGE = list(languages.transcription_lang.keys())[1] self.INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1]
self.INPUT_SPEAKER_ENERGY_THRESHOLD = 300 self.INPUT_SPEAKER_ENERGY_THRESHOLD = 300
self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True
self.INPUT_SPEAKER_RECORD_TIMEOUT = 3 self.INPUT_SPEAKER_RECORD_TIMEOUT = 3
@@ -66,24 +75,30 @@ class App(customtkinter.CTk):
"Google(web)": None, "Google(web)": None,
} }
self.MESSAGE_FORMAT = "[message]([translation])" self.MESSAGE_FORMAT = "[message]([translation])"
# Others
self.ENABLE_AUTO_CLEAR_CHATBOX = False
# load config # load config
if os.path.isfile(self.PATH_CONFIG) is not False: if os_path.isfile(self.PATH_CONFIG) is not False:
with open(self.PATH_CONFIG, 'r') as fp: with open(self.PATH_CONFIG, 'r') as fp:
config = json.load(fp) config = json_load(fp)
# main window # main window
if "ENABLE_TRANSLATION" in config.keys(): # main windowは初期はすべてOFFにする
if type(config["ENABLE_TRANSLATION"]) is bool: # if "ENABLE_TRANSLATION" in config.keys():
self.ENABLE_TRANSLATION = config["ENABLE_TRANSLATION"] # if type(config["ENABLE_TRANSLATION"]) is bool:
if "ENABLE_TRANSCRIPTION_SEND" in config.keys(): # self.ENABLE_TRANSLATION = config["ENABLE_TRANSLATION"]
if type(config["ENABLE_TRANSCRIPTION_SEND"]) is bool:
self.ENABLE_TRANSCRIPTION_SEND = config["ENABLE_TRANSCRIPTION_SEND"] # 環境に依ってマイクとスピーカーを同時起動するとエラーが発生するため、起動時は強制的にOFFにする
if "ENABLE_TRANSCRIPTION_RECEIVE" in config.keys(): # if "ENABLE_TRANSCRIPTION_SEND" in config.keys():
if type(config["ENABLE_TRANSCRIPTION_RECEIVE"]) is bool: # if type(config["ENABLE_TRANSCRIPTION_SEND"]) is bool:
self.ENABLE_TRANSCRIPTION_RECEIVE = config["ENABLE_TRANSCRIPTION_RECEIVE"] # self.ENABLE_TRANSCRIPTION_SEND = config["ENABLE_TRANSCRIPTION_SEND"]
if "ENABLE_FOREGROUND" in config.keys(): # if "ENABLE_TRANSCRIPTION_RECEIVE" in config.keys():
if type(config["ENABLE_FOREGROUND"]) is bool: # if type(config["ENABLE_TRANSCRIPTION_RECEIVE"]) is bool:
self.ENABLE_FOREGROUND = config["ENABLE_FOREGROUND"] # self.ENABLE_TRANSCRIPTION_RECEIVE = config["ENABLE_TRANSCRIPTION_RECEIVE"]
# if "ENABLE_FOREGROUND" in config.keys():
# if type(config["ENABLE_FOREGROUND"]) is bool:
# self.ENABLE_FOREGROUND = config["ENABLE_FOREGROUND"]
# tab ui # tab ui
if "TRANSPARENCY" in config.keys(): if "TRANSPARENCY" in config.keys():
@@ -99,30 +114,36 @@ class App(customtkinter.CTk):
if "FONT_FAMILY" in config.keys(): if "FONT_FAMILY" in config.keys():
if config["FONT_FAMILY"] in list(tk.font.families()): if config["FONT_FAMILY"] in list(tk.font.families()):
self.FONT_FAMILY = config["FONT_FAMILY"] self.FONT_FAMILY = config["FONT_FAMILY"]
if "UI_LANGUAGE" in config.keys():
if config["UI_LANGUAGE"] in list(selectable_languages.keys()):
self.UI_LANGUAGE = config["UI_LANGUAGE"]
# translation # translation
if "CHOICE_TRANSLATOR" in config.keys(): if "CHOICE_TRANSLATOR" in config.keys():
if config["CHOICE_TRANSLATOR"] in list(self.translator.translator_status.keys()): if config["CHOICE_TRANSLATOR"] in list(self.translator.translator_status.keys()):
self.CHOICE_TRANSLATOR = config["CHOICE_TRANSLATOR"] self.CHOICE_TRANSLATOR = config["CHOICE_TRANSLATOR"]
if "INPUT_SOURCE_LANG" in config.keys(): if "INPUT_SOURCE_LANG" in config.keys():
if config["INPUT_SOURCE_LANG"] in list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys()): if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()):
self.INPUT_SOURCE_LANG = config["INPUT_SOURCE_LANG"] self.INPUT_SOURCE_LANG = config["INPUT_SOURCE_LANG"]
if "INPUT_TARGET_LANG" in config.keys(): if "INPUT_TARGET_LANG" in config.keys():
if config["INPUT_SOURCE_LANG"] in list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys()): if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()):
self.INPUT_TARGET_LANG = config["INPUT_TARGET_LANG"] self.INPUT_TARGET_LANG = config["INPUT_TARGET_LANG"]
if "OUTPUT_SOURCE_LANG" in config.keys(): if "OUTPUT_SOURCE_LANG" in config.keys():
if config["INPUT_SOURCE_LANG"] in list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys()): if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()):
self.OUTPUT_SOURCE_LANG = config["OUTPUT_SOURCE_LANG"] self.OUTPUT_SOURCE_LANG = config["OUTPUT_SOURCE_LANG"]
if "OUTPUT_TARGET_LANG" in config.keys(): if "OUTPUT_TARGET_LANG" in config.keys():
if config["INPUT_SOURCE_LANG"] in list(languages.translation_lang[self.CHOICE_TRANSLATOR].keys()): if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR].keys()):
self.OUTPUT_TARGET_LANG = config["OUTPUT_TARGET_LANG"] self.OUTPUT_TARGET_LANG = config["OUTPUT_TARGET_LANG"]
# Transcription # Transcription
if "CHOICE_MIC_HOST" in config.keys():
if config["CHOICE_MIC_HOST"] in [host for host in get_input_device_list().keys()]:
self.CHOICE_MIC_HOST = config["CHOICE_MIC_HOST"]
if "CHOICE_MIC_DEVICE" in config.keys(): if "CHOICE_MIC_DEVICE" in config.keys():
if config["CHOICE_MIC_DEVICE"] in [device["name"] for device in audio_utils.get_input_device_list()]: if config["CHOICE_MIC_DEVICE"] in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]:
self.CHOICE_MIC_DEVICE = config["CHOICE_MIC_DEVICE"] self.CHOICE_MIC_DEVICE = config["CHOICE_MIC_DEVICE"]
if "INPUT_MIC_VOICE_LANGUAGE" in config.keys(): if "INPUT_MIC_VOICE_LANGUAGE" in config.keys():
if config["INPUT_MIC_VOICE_LANGUAGE"] in list(languages.transcription_lang.keys()): if config["INPUT_MIC_VOICE_LANGUAGE"] in list(transcription_lang.keys()):
self.INPUT_MIC_VOICE_LANGUAGE = config["INPUT_MIC_VOICE_LANGUAGE"] self.INPUT_MIC_VOICE_LANGUAGE = config["INPUT_MIC_VOICE_LANGUAGE"]
if "INPUT_MIC_ENERGY_THRESHOLD" in config.keys(): if "INPUT_MIC_ENERGY_THRESHOLD" in config.keys():
if type(config["INPUT_MIC_ENERGY_THRESHOLD"]) is int: if type(config["INPUT_MIC_ENERGY_THRESHOLD"]) is int:
@@ -139,12 +160,17 @@ class App(customtkinter.CTk):
if "INPUT_MIC_MAX_PHRASES" in config.keys(): if "INPUT_MIC_MAX_PHRASES" in config.keys():
if type(config["INPUT_MIC_MAX_PHRASES"]) is int: if type(config["INPUT_MIC_MAX_PHRASES"]) is int:
self.INPUT_MIC_MAX_PHRASES = config["INPUT_MIC_MAX_PHRASES"] self.INPUT_MIC_MAX_PHRASES = config["INPUT_MIC_MAX_PHRASES"]
if "INPUT_MIC_WORD_FILTER" in config.keys():
if type(config["INPUT_MIC_WORD_FILTER"]) is list:
self.INPUT_MIC_WORD_FILTER = config["INPUT_MIC_WORD_FILTER"]
if "CHOICE_SPEAKER_DEVICE" in config.keys(): if "CHOICE_SPEAKER_DEVICE" in config.keys():
if config["CHOICE_SPEAKER_DEVICE"] in [device["name"] for device in audio_utils.get_output_device_list()]: if config["CHOICE_SPEAKER_DEVICE"] in [device["name"] for device in get_output_device_list()]:
self.CHOICE_SPEAKER_DEVICE = config["CHOICE_SPEAKER_DEVICE"] speaker_device = [device for device in get_output_device_list() if device["name"] == config["CHOICE_SPEAKER_DEVICE"]][0]
if get_default_output_device()["index"] == speaker_device["index"]:
self.CHOICE_SPEAKER_DEVICE = config["CHOICE_SPEAKER_DEVICE"]
if "INPUT_SPEAKER_VOICE_LANGUAGE" in config.keys(): if "INPUT_SPEAKER_VOICE_LANGUAGE" in config.keys():
if config["INPUT_SPEAKER_VOICE_LANGUAGE"] in list(languages.transcription_lang.keys()): if config["INPUT_SPEAKER_VOICE_LANGUAGE"] in list(transcription_lang.keys()):
self.INPUT_SPEAKER_VOICE_LANGUAGE = config["INPUT_SPEAKER_VOICE_LANGUAGE"] self.INPUT_SPEAKER_VOICE_LANGUAGE = config["INPUT_SPEAKER_VOICE_LANGUAGE"]
if "INPUT_SPEAKER_ENERGY_THRESHOLD" in config.keys(): if "INPUT_SPEAKER_ENERGY_THRESHOLD" in config.keys():
if type(config["INPUT_SPEAKER_ENERGY_THRESHOLD"]) is int: if type(config["INPUT_SPEAKER_ENERGY_THRESHOLD"]) is int:
@@ -179,21 +205,28 @@ class App(customtkinter.CTk):
if type(config["MESSAGE_FORMAT"]) is str: if type(config["MESSAGE_FORMAT"]) is str:
self.MESSAGE_FORMAT = config["MESSAGE_FORMAT"] self.MESSAGE_FORMAT = config["MESSAGE_FORMAT"]
# Others
if "ENABLE_AUTO_CLEAR_CHATBOX" in config.keys():
if type(config["ENABLE_AUTO_CLEAR_CHATBOX"]) is bool:
self.ENABLE_AUTO_CLEAR_CHATBOX = config["ENABLE_AUTO_CLEAR_CHATBOX"]
with open(self.PATH_CONFIG, 'w') as fp: with open(self.PATH_CONFIG, 'w') as fp:
config = { config = {
"ENABLE_TRANSLATION": self.ENABLE_TRANSLATION, # "ENABLE_TRANSLATION": self.ENABLE_TRANSLATION,
"ENABLE_TRANSCRIPTION_SEND": self.ENABLE_TRANSCRIPTION_SEND, # "ENABLE_TRANSCRIPTION_SEND": self.ENABLE_TRANSCRIPTION_SEND,
"ENABLE_TRANSCRIPTION_RECEIVE": self.ENABLE_TRANSCRIPTION_RECEIVE, # "ENABLE_TRANSCRIPTION_RECEIVE": self.ENABLE_TRANSCRIPTION_RECEIVE,
"ENABLE_FOREGROUND": self.ENABLE_FOREGROUND, # "ENABLE_FOREGROUND": self.ENABLE_FOREGROUND,
"TRANSPARENCY": self.TRANSPARENCY, "TRANSPARENCY": self.TRANSPARENCY,
"APPEARANCE_THEME": self.APPEARANCE_THEME, "APPEARANCE_THEME": self.APPEARANCE_THEME,
"UI_SCALING": self.UI_SCALING, "UI_SCALING": self.UI_SCALING,
"UI_LANGUAGE": self.UI_LANGUAGE,
"FONT_FAMILY": self.FONT_FAMILY, "FONT_FAMILY": self.FONT_FAMILY,
"CHOICE_TRANSLATOR": self.CHOICE_TRANSLATOR, "CHOICE_TRANSLATOR": self.CHOICE_TRANSLATOR,
"INPUT_SOURCE_LANG": self.INPUT_SOURCE_LANG, "INPUT_SOURCE_LANG": self.INPUT_SOURCE_LANG,
"INPUT_TARGET_LANG": self.INPUT_TARGET_LANG, "INPUT_TARGET_LANG": self.INPUT_TARGET_LANG,
"OUTPUT_SOURCE_LANG": self.OUTPUT_SOURCE_LANG, "OUTPUT_SOURCE_LANG": self.OUTPUT_SOURCE_LANG,
"OUTPUT_TARGET_LANG": self.OUTPUT_TARGET_LANG, "OUTPUT_TARGET_LANG": self.OUTPUT_TARGET_LANG,
"CHOICE_MIC_HOST": self.CHOICE_MIC_HOST,
"CHOICE_MIC_DEVICE": self.CHOICE_MIC_DEVICE, "CHOICE_MIC_DEVICE": self.CHOICE_MIC_DEVICE,
"INPUT_MIC_VOICE_LANGUAGE": self.INPUT_MIC_VOICE_LANGUAGE, "INPUT_MIC_VOICE_LANGUAGE": self.INPUT_MIC_VOICE_LANGUAGE,
"INPUT_MIC_ENERGY_THRESHOLD": self.INPUT_MIC_ENERGY_THRESHOLD, "INPUT_MIC_ENERGY_THRESHOLD": self.INPUT_MIC_ENERGY_THRESHOLD,
@@ -201,6 +234,7 @@ class App(customtkinter.CTk):
"INPUT_MIC_RECORD_TIMEOUT": self.INPUT_MIC_RECORD_TIMEOUT, "INPUT_MIC_RECORD_TIMEOUT": self.INPUT_MIC_RECORD_TIMEOUT,
"INPUT_MIC_PHRASE_TIMEOUT": self.INPUT_MIC_PHRASE_TIMEOUT, "INPUT_MIC_PHRASE_TIMEOUT": self.INPUT_MIC_PHRASE_TIMEOUT,
"INPUT_MIC_MAX_PHRASES": self.INPUT_MIC_MAX_PHRASES, "INPUT_MIC_MAX_PHRASES": self.INPUT_MIC_MAX_PHRASES,
"INPUT_MIC_WORD_FILTER": self.INPUT_MIC_WORD_FILTER,
"CHOICE_SPEAKER_DEVICE": self.CHOICE_SPEAKER_DEVICE, "CHOICE_SPEAKER_DEVICE": self.CHOICE_SPEAKER_DEVICE,
"INPUT_SPEAKER_VOICE_LANGUAGE": self.INPUT_SPEAKER_VOICE_LANGUAGE, "INPUT_SPEAKER_VOICE_LANGUAGE": self.INPUT_SPEAKER_VOICE_LANGUAGE,
"INPUT_SPEAKER_ENERGY_THRESHOLD": self.INPUT_SPEAKER_ENERGY_THRESHOLD, "INPUT_SPEAKER_ENERGY_THRESHOLD": self.INPUT_SPEAKER_ENERGY_THRESHOLD,
@@ -212,15 +246,16 @@ class App(customtkinter.CTk):
"OSC_PORT": self.OSC_PORT, "OSC_PORT": self.OSC_PORT,
"AUTH_KEYS": self.AUTH_KEYS, "AUTH_KEYS": self.AUTH_KEYS,
"MESSAGE_FORMAT": self.MESSAGE_FORMAT, "MESSAGE_FORMAT": self.MESSAGE_FORMAT,
"ENABLE_AUTO_CLEAR_CHATBOX": self.ENABLE_AUTO_CLEAR_CHATBOX,
} }
json.dump(config, fp, indent=4) json_dump(config, fp, indent=4)
## set UI theme ## set UI theme
customtkinter.set_appearance_mode(self.APPEARANCE_THEME) customtkinter.set_appearance_mode(self.APPEARANCE_THEME)
customtkinter.set_default_color_theme("blue") customtkinter.set_default_color_theme("blue")
# init main window # init main window
self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico")) self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico"))
self.title("VRCT") self.title("VRCT")
self.geometry(f"{400}x{175}") self.geometry(f"{400}x{175}")
self.minsize(400, 175) self.minsize(400, 175)
@@ -228,132 +263,87 @@ class App(customtkinter.CTk):
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
# add sidebar left # add sidebar left
self.sidebar_frame = customtkinter.CTkFrame(self, corner_radius=0) self.sidebar_frame = CTkFrame(self, corner_radius=0)
self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw") self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw")
self.sidebar_frame.grid_rowconfigure(5, weight=1) self.sidebar_frame.grid_rowconfigure(5, weight=1)
init_lang_text = "Loading..."
# add checkbox translation # add checkbox translation
self.checkbox_translation = customtkinter.CTkCheckBox( self.checkbox_translation = CTkCheckBox(
self.sidebar_frame, self.sidebar_frame,
text="translation", text=init_lang_text,
onvalue=True, onvalue=True,
offvalue=False, offvalue=False,
command=self.checkbox_translation_callback, command=self.checkbox_translation_callback,
font=customtkinter.CTkFont(family=self.FONT_FAMILY) font=CTkFont(family=self.FONT_FAMILY)
) )
self.checkbox_translation.grid(row=0, column=0, columnspan=2 ,padx=10, pady=(5, 5), sticky="we") self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we")
# add checkbox transcription send # add checkbox transcription send
self.checkbox_transcription_send = customtkinter.CTkCheckBox( self.checkbox_transcription_send = CTkCheckBox(
self.sidebar_frame, self.sidebar_frame,
text="voice2chatbox", text=init_lang_text,
onvalue=True, onvalue=True,
offvalue=False, offvalue=False,
command=self.checkbox_transcription_send_callback, command=self.checkbox_transcription_send_callback,
font=customtkinter.CTkFont(family=self.FONT_FAMILY) font=CTkFont(family=self.FONT_FAMILY)
) )
self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2 ,padx=10, pady=(5, 5), sticky="we") self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we")
# add checkbox transcription receive # add checkbox transcription receive
self.checkbox_transcription_receive = customtkinter.CTkCheckBox( self.checkbox_transcription_receive = CTkCheckBox(
self.sidebar_frame, self.sidebar_frame,
text="speaker2log", text=init_lang_text,
onvalue=True, onvalue=True,
offvalue=False, offvalue=False,
command=self.checkbox_transcription_receive_callback, command=self.checkbox_transcription_receive_callback,
font=customtkinter.CTkFont(family=self.FONT_FAMILY) font=CTkFont(family=self.FONT_FAMILY)
) )
self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2 ,padx=10, pady=(5, 5), sticky="we") self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we")
# add checkbox foreground # add checkbox foreground
self.checkbox_foreground = customtkinter.CTkCheckBox( self.checkbox_foreground = CTkCheckBox(
self.sidebar_frame, self.sidebar_frame,
text="foreground", text=init_lang_text,
onvalue=True, onvalue=True,
offvalue=False, offvalue=False,
command=self.checkbox_foreground_callback, command=self.checkbox_foreground_callback,
font=customtkinter.CTkFont(family=self.FONT_FAMILY) font=CTkFont(family=self.FONT_FAMILY)
) )
self.checkbox_foreground.grid(row=3, column=0, columnspan=2 ,padx=10, pady=(5, 5), sticky="we") self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we")
# add button information # add button information
self.button_information = customtkinter.CTkButton( self.button_information = CTkButton(
self.sidebar_frame, self.sidebar_frame,
text="", text="",
width=25, width=25,
command=self.button_information_callback, command=self.button_information_callback,
image=customtkinter.CTkImage(Image.open(os.path.join(os.path.dirname(__file__), "img", "info-icon-white.png"))) image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png")))
) )
self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse")
self.information_window = None self.information_window = None
# add button config # add button config
self.button_config = customtkinter.CTkButton( self.button_config = CTkButton(
self.sidebar_frame, self.sidebar_frame,
text="", text="",
width=25, width=25,
command=self.button_config_callback, command=self.button_config_callback,
image=customtkinter.CTkImage(Image.open(os.path.join(os.path.dirname(__file__), "img", "config-icon-white.png"))) image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png")))
) )
self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse")
self.config_window = None
# load ui language data
language_yaml_data = get_localized_text(f"{self.UI_LANGUAGE}")
# add tabview textbox # add tabview textbox
self.tabview_logs = customtkinter.CTkTabview(master=self) self.add_tabview_logs(language_yaml_data)
self.tabview_logs.add("log")
self.tabview_logs.add("send")
self.tabview_logs.add("receive")
self.tabview_logs.add("system")
self.tabview_logs.grid(row=0, column=1, padx=0, pady=0, sticky="nsew")
self.tabview_logs._segmented_button.configure(font=customtkinter.CTkFont(family=self.FONT_FAMILY))
self.tabview_logs._segmented_button.grid(sticky="W")
self.tabview_logs.tab("log").grid_rowconfigure(0, weight=1)
self.tabview_logs.tab("log").grid_columnconfigure(0, weight=1)
self.tabview_logs.tab("send").grid_rowconfigure(0, weight=1)
self.tabview_logs.tab("send").grid_columnconfigure(0, weight=1)
self.tabview_logs.tab("receive").grid_rowconfigure(0, weight=1)
self.tabview_logs.tab("receive").grid_columnconfigure(0, weight=1)
self.tabview_logs.tab("system").grid_rowconfigure(0, weight=1)
self.tabview_logs.tab("system").grid_columnconfigure(0, weight=1)
self.tabview_logs.configure(fg_color="transparent")
# add textbox message log
self.textbox_message_log = customtkinter.CTkTextbox(
self.tabview_logs.tab("log"),
font=customtkinter.CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_log.configure(state='disabled')
# add textbox message send log
self.textbox_message_send_log = customtkinter.CTkTextbox(
self.tabview_logs.tab("send"),
font=customtkinter.CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_send_log.configure(state='disabled')
# add textbox message receive log
self.textbox_message_receive_log = customtkinter.CTkTextbox(
self.tabview_logs.tab("receive"),
font=customtkinter.CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_receive_log.configure(state='disabled')
# add textbox message system log
self.textbox_message_system_log = customtkinter.CTkTextbox(
self.tabview_logs.tab("system"),
font=customtkinter.CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_system_log.configure(state='disabled')
# add entry message box # add entry message box
self.entry_message_box = customtkinter.CTkEntry( self.entry_message_box = CTkEntry(
self, self,
placeholder_text="message", placeholder_text="message",
font=customtkinter.CTkFont(family=self.FONT_FAMILY) font=CTkFont(family=self.FONT_FAMILY),
) )
self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew") self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew")
@@ -361,36 +351,40 @@ class App(customtkinter.CTk):
## set translator ## set translator
if self.translator.authentication(self.CHOICE_TRANSLATOR, self.AUTH_KEYS[self.CHOICE_TRANSLATOR]) is False: if self.translator.authentication(self.CHOICE_TRANSLATOR, self.AUTH_KEYS[self.CHOICE_TRANSLATOR]) is False:
# error update Auth key # error update Auth key
utils.print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR")
utils.print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR")
## set checkbox enable translation # ## set checkbox enable translation
if self.ENABLE_TRANSLATION: # if self.ENABLE_TRANSLATION:
self.checkbox_translation.select() # self.checkbox_translation.select()
self.checkbox_translation_callback() # self.checkbox_translation_callback()
else: # else:
self.checkbox_translation.deselect() # self.checkbox_translation.deselect()
## set checkbox enable transcription send # ## set checkbox enable transcription send
if self.ENABLE_TRANSCRIPTION_SEND: # if self.ENABLE_TRANSCRIPTION_SEND:
self.checkbox_transcription_send.select() # self.checkbox_transcription_send.select()
self.checkbox_transcription_send_callback() # self.checkbox_transcription_send_callback()
else: # else:
self.checkbox_transcription_send.deselect() # self.checkbox_transcription_send.deselect()
## set checkbox enable transcription receive # ## set checkbox enable transcription receive
if self.ENABLE_TRANSCRIPTION_RECEIVE: # if self.ENABLE_TRANSCRIPTION_RECEIVE:
self.checkbox_transcription_receive.select() # self.checkbox_transcription_receive.select()
self.checkbox_transcription_receive_callback() # self.checkbox_transcription_receive_callback()
else: # else:
self.checkbox_transcription_receive.deselect() # self.checkbox_transcription_receive.deselect()
## set set checkbox enable foreground # ## set set checkbox enable foreground
if self.ENABLE_FOREGROUND: # if self.ENABLE_FOREGROUND:
self.checkbox_foreground.select() # self.checkbox_foreground.select()
self.checkbox_foreground_callback() # self.checkbox_foreground_callback()
else: # else:
self.checkbox_foreground.deselect() # self.checkbox_foreground.deselect()
## set word filter
for f in self.INPUT_MIC_WORD_FILTER:
self.keyword_processor.add_keyword(f)
## set bind entry message box ## set bind entry message box
self.entry_message_box.bind("<Return>", self.entry_message_box_press_key_enter) self.entry_message_box.bind("<Return>", self.entry_message_box_press_key_enter)
@@ -407,119 +401,142 @@ class App(customtkinter.CTk):
# delete window # delete window
self.protocol("WM_DELETE_WINDOW", self.delete_window) self.protocol("WM_DELETE_WINDOW", self.delete_window)
self.config_window = ToplevelWindowConfig(self)
def button_config_callback(self): def button_config_callback(self):
if self.config_window is None or not self.config_window.winfo_exists(): self.checkbox_translation.configure(state="disabled")
self.config_window = window_config.ToplevelWindowConfig(self) self.checkbox_transcription_send.configure(state="disabled")
self.checkbox_translation.configure(state="disabled") self.checkbox_transcription_receive.configure(state="disabled")
self.checkbox_transcription_send.configure(state="disabled") self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"])
self.checkbox_transcription_receive.configure(state="disabled")
self.config_window.deiconify()
self.config_window.focus_set()
self.config_window.focus() self.config_window.focus()
def button_information_callback(self): def button_information_callback(self):
if self.information_window is None or not self.information_window.winfo_exists(): if self.information_window is None or not self.information_window.winfo_exists():
self.information_window = window_information.ToplevelWindowInformation(self) self.information_window = ToplevelWindowInformation(self)
self.information_window.focus() self.information_window.focus()
def checkbox_translation_callback(self): def checkbox_translation_callback(self):
self.ENABLE_TRANSLATION = self.checkbox_translation.get() self.ENABLE_TRANSLATION = self.checkbox_translation.get()
if self.ENABLE_TRANSLATION: if self.ENABLE_TRANSLATION:
self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"])
utils.print_textbox(self.textbox_message_log, "Start translation", "INFO") print_textbox(self.textbox_message_log, "Start translation", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Start translation", "INFO") print_textbox(self.textbox_message_system_log, "Start translation", "INFO")
else: else:
if ((self.checkbox_translation.get() is False) and if ((self.checkbox_translation.get() is False) and
(self.checkbox_transcription_send.get() is False) and (self.checkbox_transcription_send.get() is False) and
(self.checkbox_transcription_receive.get() is False)): (self.checkbox_transcription_receive.get() is False)):
self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"])
utils.print_textbox(self.textbox_message_log, "Stop translation", "INFO") print_textbox(self.textbox_message_log, "Stop translation", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") print_textbox(self.textbox_message_system_log, "Stop translation", "INFO")
utils.save_json(self.PATH_CONFIG, "ENABLE_TRANSLATION", self.ENABLE_TRANSLATION) save_json(self.PATH_CONFIG, "ENABLE_TRANSLATION", self.ENABLE_TRANSLATION)
def transcription_send_start(self):
self.mic_audio_queue = Queue()
mic_device = [device for device in get_input_device_list()[self.CHOICE_MIC_HOST] if device["name"] == self.CHOICE_MIC_DEVICE][0]
self.mic_audio_recorder = SelectedMicRecorder(
mic_device,
self.INPUT_MIC_ENERGY_THRESHOLD,
self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD,
self.INPUT_MIC_RECORD_TIMEOUT,
)
self.mic_audio_recorder.record_into_queue(self.mic_audio_queue)
self.mic_transcriber = AudioTranscriber(
speaker=False,
source=self.mic_audio_recorder.source,
language=transcription_lang[self.INPUT_MIC_VOICE_LANGUAGE],
phrase_timeout=self.INPUT_MIC_PHRASE_TIMEOUT,
max_phrases=self.INPUT_MIC_MAX_PHRASES,
)
def mic_transcript_to_chatbox():
self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue)
message = self.mic_transcriber.get_transcript()
if len(message) > 0:
# word filter
if len(self.keyword_processor.extract_keywords(message)) != 0:
print_textbox(self.textbox_message_log, f"Detect WordFilter :{message}", "INFO")
print_textbox(self.textbox_message_system_log, f"Detect WordFilter :{message}", "INFO")
return
# translate
if self.checkbox_translation.get() is False:
voice_message = f"{message}"
elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False:
print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR")
print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR")
voice_message = f"{message}"
else:
result = self.translator.translate(
translator_name=self.CHOICE_TRANSLATOR,
source_language=self.INPUT_SOURCE_LANG,
target_language=self.INPUT_TARGET_LANG,
message=message
)
voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result)
if self.checkbox_transcription_send.get() is True:
# send OSC message
send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT)
# update textbox message log
print_textbox(self.textbox_message_log, f"{voice_message}", "SEND")
print_textbox(self.textbox_message_send_log, f"{voice_message}", "SEND")
self.mic_print_transcript = thread_fnc(mic_transcript_to_chatbox)
self.mic_print_transcript.daemon = True
self.mic_print_transcript.start()
print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO")
print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO")
self.checkbox_transcription_send.configure(state="normal")
self.checkbox_transcription_receive.configure(state="normal")
def transcription_send_stop(self):
if isinstance(self.mic_print_transcript, thread_fnc):
self.mic_print_transcript.stop()
if self.mic_audio_recorder.stop != None:
self.mic_audio_recorder.stop()
self.mic_audio_recorder.stop = None
print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO")
print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO")
if ((self.checkbox_translation.get() is False) and
(self.checkbox_transcription_send.get() is False) and
(self.checkbox_transcription_receive.get() is False)):
self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"])
self.checkbox_transcription_send.configure(state="normal")
self.checkbox_transcription_receive.configure(state="normal")
def checkbox_transcription_send_callback(self): def checkbox_transcription_send_callback(self):
self.checkbox_transcription_send.configure(state="disabled")
self.checkbox_transcription_receive.configure(state="disabled")
self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"])
self.update()
self.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() self.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get()
if self.ENABLE_TRANSCRIPTION_SEND is True: if self.ENABLE_TRANSCRIPTION_SEND is True:
self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) th_transcription_send_start = Thread(target=self.transcription_send_start)
self.mic_audio_queue = queue.Queue() th_transcription_send_start.daemon = True
mic_device = [device for device in audio_utils.get_input_device_list() if device["name"] == self.CHOICE_MIC_DEVICE][0] th_transcription_send_start.start()
self.mic_audio_recorder = audio_recorder.SelectedMicRecorder(
mic_device,
self.INPUT_MIC_ENERGY_THRESHOLD,
self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD,
self.INPUT_MIC_RECORD_TIMEOUT,
)
self.mic_audio_recorder.record_into_queue(self.mic_audio_queue)
self.mic_transcriber = audio_transcriber.AudioTranscriber(
speaker=False,
source=self.mic_audio_recorder.source,
language=languages.transcription_lang[self.INPUT_MIC_VOICE_LANGUAGE],
phrase_timeout=self.INPUT_MIC_PHRASE_TIMEOUT,
max_phrases=self.INPUT_MIC_MAX_PHRASES,
)
def mic_transcript_to_chatbox():
self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue)
message = self.mic_transcriber.get_transcript()
if len(message) > 0:
# translate
if self.checkbox_translation.get() is False:
voice_message = f"{message}"
elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False:
utils.print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR")
utils.print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR")
voice_message = f"{message}"
else:
result = self.translator.translate(
translator_name=self.CHOICE_TRANSLATOR,
source_language=self.INPUT_SOURCE_LANG,
target_language=self.INPUT_TARGET_LANG,
message=message
)
voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result)
if self.checkbox_transcription_send.get() is True:
# send OSC message
osc_tools.send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT)
# update textbox message log
utils.print_textbox(self.textbox_message_log, f"{voice_message}", "SEND")
utils.print_textbox(self.textbox_message_send_log, f"{voice_message}", "SEND")
self.mic_print_transcript = utils.thread_fnc(mic_transcript_to_chatbox)
self.mic_print_transcript.daemon = True
self.mic_print_transcript.start()
utils.print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO")
else: else:
if ((self.checkbox_translation.get() is False) and th_transcription_send_stop = Thread(target=self.transcription_send_stop)
(self.checkbox_transcription_send.get() is False) and th_transcription_send_stop.daemon = True
(self.checkbox_transcription_receive.get() is False)): th_transcription_send_stop.start()
self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) save_json(self.PATH_CONFIG, "ENABLE_TRANSCRIPTION_SEND", self.ENABLE_TRANSCRIPTION_SEND)
if isinstance(self.mic_print_transcript, utils.thread_fnc):
self.mic_print_transcript.stop()
if self.mic_audio_recorder.stop != None:
self.mic_audio_recorder.stop()
self.mic_audio_recorder.stop = None
utils.print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") def transcription_receive_start(self):
utils.print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") self.spk_audio_queue = Queue()
utils.save_json(self.PATH_CONFIG, "ENABLE_TRANSCRIPTION_SEND", self.ENABLE_TRANSCRIPTION_SEND) spk_device = [device for device in get_output_device_list() if device["name"] == self.CHOICE_SPEAKER_DEVICE][0]
self.spk_audio_recorder = SelectedSpeakerRecorder(
def checkbox_transcription_receive_callback(self):
self.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get()
if self.ENABLE_TRANSCRIPTION_RECEIVE is True:
self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"])
self.spk_audio_queue = queue.Queue()
spk_device = [device for device in audio_utils.get_output_device_list() if device["name"] == self.CHOICE_SPEAKER_DEVICE][0]
self.spk_audio_recorder = audio_recorder.SelectedSpeakerRecorder(
spk_device, spk_device,
self.INPUT_SPEAKER_ENERGY_THRESHOLD, self.INPUT_SPEAKER_ENERGY_THRESHOLD,
self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD,
self.INPUT_SPEAKER_RECORD_TIMEOUT, self.INPUT_SPEAKER_RECORD_TIMEOUT,
) )
self.spk_audio_recorder.record_into_queue(self.spk_audio_queue) self.spk_audio_recorder.record_into_queue(self.spk_audio_queue)
self.spk_transcriber = audio_transcriber.AudioTranscriber( self.spk_transcriber = AudioTranscriber(
speaker=True, speaker=True,
source=self.spk_audio_recorder.source, source=self.spk_audio_recorder.source,
language=languages.transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE], language=transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE],
phrase_timeout=self.INPUT_SPEAKER_PHRASE_TIMEOUT, phrase_timeout=self.INPUT_SPEAKER_PHRASE_TIMEOUT,
max_phrases=self.INPUT_SPEAKER_MAX_PHRASES, max_phrases=self.INPUT_SPEAKER_MAX_PHRASES,
) )
@@ -532,8 +549,8 @@ class App(customtkinter.CTk):
if self.checkbox_translation.get() is False: if self.checkbox_translation.get() is False:
voice_message = f"{message}" voice_message = f"{message}"
elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False:
utils.print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR")
utils.print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR")
voice_message = f"{message}" voice_message = f"{message}"
else: else:
result = self.translator.translate( result = self.translator.translate(
@@ -544,47 +561,69 @@ class App(customtkinter.CTk):
) )
voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result)
# send OSC message # send OSC message
# osc_tools.send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) # send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT)
if self.checkbox_transcription_receive.get() is True: if self.checkbox_transcription_receive.get() is True:
# update textbox message receive log # update textbox message receive log
utils.print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE") print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE")
utils.print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE") print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE")
self.spk_print_transcript = utils.thread_fnc(spk_transcript_to_textbox) self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox)
self.spk_print_transcript.daemon = True self.spk_print_transcript.daemon = True
self.spk_print_transcript.start() self.spk_print_transcript.start()
utils.print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") print_textbox(self.textbox_message_log, "Start speaker2log", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO")
self.checkbox_transcription_send.configure(state="normal")
self.checkbox_transcription_receive.configure(state="normal")
def transcription_receive_stop(self):
if isinstance(self.spk_print_transcript, thread_fnc):
self.spk_print_transcript.stop()
if self.spk_audio_recorder.stop != None:
self.spk_audio_recorder.stop()
self.spk_audio_recorder.stop = None
print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO")
print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO")
if ((self.checkbox_translation.get() is False) and
(self.checkbox_transcription_send.get() is False) and
(self.checkbox_transcription_receive.get() is False)):
self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"])
self.checkbox_transcription_send.configure(state="normal")
self.checkbox_transcription_receive.configure(state="normal")
def checkbox_transcription_receive_callback(self):
self.checkbox_transcription_send.configure(state="disabled")
self.checkbox_transcription_receive.configure(state="disabled")
self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"])
self.update()
self.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get()
if self.ENABLE_TRANSCRIPTION_RECEIVE is True:
th_transcription_receive_start = Thread(target=self.transcription_receive_start)
th_transcription_receive_start.daemon = True
th_transcription_receive_start.start()
else: else:
if ((self.checkbox_translation.get() is False) and th_transcription_receive_stop = Thread(target=self.transcription_receive_stop)
(self.checkbox_transcription_send.get() is False) and th_transcription_receive_stop.daemon = True
(self.checkbox_transcription_receive.get() is False)): th_transcription_receive_stop.start()
self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"])
if isinstance(self.spk_print_transcript, utils.thread_fnc): save_json(self.PATH_CONFIG, "ENABLE_TRANSCRIPTION_RECEIVE", self.ENABLE_TRANSCRIPTION_RECEIVE)
self.spk_print_transcript.stop()
if self.spk_audio_recorder.stop != None:
self.spk_audio_recorder.stop()
self.spk_audio_recorder.stop = None
utils.print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO")
utils.save_json(self.PATH_CONFIG, "ENABLE_TRANSCRIPTION_RECEIVE", self.ENABLE_TRANSCRIPTION_RECEIVE)
def checkbox_foreground_callback(self): def checkbox_foreground_callback(self):
self.ENABLE_FOREGROUND = self.checkbox_foreground.get() self.ENABLE_FOREGROUND = self.checkbox_foreground.get()
if self.ENABLE_FOREGROUND: if self.ENABLE_FOREGROUND:
self.attributes("-topmost", True) self.attributes("-topmost", True)
utils.print_textbox(self.textbox_message_log, "Start foreground", "INFO") print_textbox(self.textbox_message_log, "Start foreground", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") print_textbox(self.textbox_message_system_log, "Start foreground", "INFO")
else: else:
self.attributes("-topmost", False) self.attributes("-topmost", False)
utils.print_textbox(self.textbox_message_log, "Stop foreground", "INFO") print_textbox(self.textbox_message_log, "Stop foreground", "INFO")
utils.print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO")
utils.save_json(self.PATH_CONFIG, "ENABLE_FOREGROUND", self.ENABLE_FOREGROUND) save_json(self.PATH_CONFIG, "ENABLE_FOREGROUND", self.ENABLE_FOREGROUND)
def entry_message_box_press_key_enter(self, event): def entry_message_box_press_key_enter(self, event):
# send OSC typing # send OSC typing
osc_tools.send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT) send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT)
if self.ENABLE_FOREGROUND: if self.ENABLE_FOREGROUND:
self.attributes("-topmost", True) self.attributes("-topmost", True)
@@ -595,8 +634,8 @@ class App(customtkinter.CTk):
if self.checkbox_translation.get() is False: if self.checkbox_translation.get() is False:
chat_message = f"{message}" chat_message = f"{message}"
elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False:
utils.print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR")
utils.print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR")
chat_message = f"{message}" chat_message = f"{message}"
else: else:
result = self.translator.translate( result = self.translator.translate(
@@ -608,24 +647,34 @@ class App(customtkinter.CTk):
chat_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) chat_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result)
# send OSC message # send OSC message
osc_tools.send_message(chat_message, self.OSC_IP_ADDRESS, self.OSC_PORT) send_message(chat_message, self.OSC_IP_ADDRESS, self.OSC_PORT)
# update textbox message log # update textbox message log
utils.print_textbox(self.textbox_message_log, f"{chat_message}", "SEND") print_textbox(self.textbox_message_log, f"{chat_message}", "SEND")
utils.print_textbox(self.textbox_message_send_log, f"{chat_message}", "SEND") print_textbox(self.textbox_message_send_log, f"{chat_message}", "SEND")
# delete message in entry message box # delete message in entry message box
# self.entry_message_box.delete(0, customtkinter.END) if self.ENABLE_AUTO_CLEAR_CHATBOX == True:
self.entry_message_box.delete(0, customtkinter.END)
BREAK_KEYSYM_LIST = [
"Delete", "Select", "Up", "Down", "Next", "End", "Print",
"Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed"
]
def entry_message_box_press_key_any(self, event): def entry_message_box_press_key_any(self, event):
# send OSC typing # send OSC typing
osc_tools.send_typing(True, self.OSC_IP_ADDRESS, self.OSC_PORT) send_typing(True, self.OSC_IP_ADDRESS, self.OSC_PORT)
if self.ENABLE_FOREGROUND: if self.ENABLE_FOREGROUND:
self.attributes("-topmost", False) self.attributes("-topmost", False)
if event.keysym != "??":
if len(event.char) != 0 and event.keysym in self.BREAK_KEYSYM_LIST:
self.entry_message_box.insert("end", event.char)
return "break"
def entry_message_box_leave(self, event): def entry_message_box_leave(self, event):
# send OSC typing # send OSC typing
osc_tools.send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT) send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT)
if self.ENABLE_FOREGROUND: if self.ENABLE_FOREGROUND:
self.attributes("-topmost", True) self.attributes("-topmost", True)
@@ -633,6 +682,71 @@ class App(customtkinter.CTk):
self.quit() self.quit()
self.destroy() self.destroy()
def delete_tabview_logs(self, pre_language_yaml_data):
self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_log"])
self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_send"])
self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_receive"])
self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_system"])
def add_tabview_logs(self, language_yaml_data):
main_tab_title_log = language_yaml_data["main_tab_title_log"]
main_tab_title_send = language_yaml_data["main_tab_title_send"]
main_tab_title_receive = language_yaml_data["main_tab_title_receive"]
main_tab_title_system = language_yaml_data["main_tab_title_system"]
# add tabview textbox
self.tabview_logs = CTkTabview(master=self)
self.tabview_logs.add(main_tab_title_log)
self.tabview_logs.add(main_tab_title_send)
self.tabview_logs.add(main_tab_title_receive)
self.tabview_logs.add(main_tab_title_system)
self.tabview_logs.grid(row=0, column=1, padx=0, pady=0, sticky="nsew")
self.tabview_logs._segmented_button.configure(font=CTkFont(family=self.FONT_FAMILY))
self.tabview_logs._segmented_button.grid(sticky="W")
self.tabview_logs.tab(main_tab_title_log).grid_rowconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_log).grid_columnconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_send).grid_rowconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_send).grid_columnconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_receive).grid_rowconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_receive).grid_columnconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_system).grid_rowconfigure(0, weight=1)
self.tabview_logs.tab(main_tab_title_system).grid_columnconfigure(0, weight=1)
self.tabview_logs.configure(fg_color="transparent")
# add textbox message log
self.textbox_message_log = CTkTextbox(
self.tabview_logs.tab(main_tab_title_log),
font=CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_log.configure(state='disabled')
# add textbox message send log
self.textbox_message_send_log = CTkTextbox(
self.tabview_logs.tab(main_tab_title_send),
font=CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_send_log.configure(state='disabled')
# add textbox message receive log
self.textbox_message_receive_log = CTkTextbox(
self.tabview_logs.tab(main_tab_title_receive),
font=CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_receive_log.configure(state='disabled')
# add textbox message system log
self.textbox_message_system_log = CTkTextbox(
self.tabview_logs.tab(main_tab_title_system),
font=CTkFont(family=self.FONT_FAMILY)
)
self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew")
self.textbox_message_system_log.configure(state='disabled')
widget_main_window_label_setter(self, language_yaml_data)
if __name__ == "__main__": if __name__ == "__main__":
try: try:
app = App() app = App()

View File

@@ -1,10 +1,10 @@
import speech_recognition as sr from speech_recognition import Recognizer, Microphone
import pyaudiowpatch as pyaudio from pyaudiowpatch import get_sample_size, paInt16
from datetime import datetime from datetime import datetime
class BaseRecorder: class BaseRecorder:
def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout): def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout):
self.recorder = sr.Recognizer() self.recorder = Recognizer()
self.recorder.energy_threshold = energy_threshold self.recorder.energy_threshold = energy_threshold
self.recorder.dynamic_energy_threshold = dynamic_energy_threshold self.recorder.dynamic_energy_threshold = dynamic_energy_threshold
self.record_timeout = record_timeout self.record_timeout = record_timeout
@@ -20,14 +20,14 @@ class BaseRecorder:
self.recorder.adjust_for_ambient_noise(self.source) self.recorder.adjust_for_ambient_noise(self.source)
def record_into_queue(self, audio_queue): def record_into_queue(self, audio_queue):
def record_callback(_, audio:sr.AudioData) -> None: def record_callback(_, audio):
audio_queue.put((audio.get_raw_data(), datetime.now())) audio_queue.put((audio.get_raw_data(), datetime.now()))
self.stop = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout) self.stop = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout)
class SelectedMicRecorder(BaseRecorder): class SelectedMicRecorder(BaseRecorder):
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
source=sr.Microphone( source=Microphone(
device_index=device['index'], device_index=device['index'],
sample_rate=int(device["defaultSampleRate"]), sample_rate=int(device["defaultSampleRate"]),
) )
@@ -37,11 +37,55 @@ class SelectedMicRecorder(BaseRecorder):
class SelectedSpeakerRecorder(BaseRecorder): class SelectedSpeakerRecorder(BaseRecorder):
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
source = sr.Microphone(speaker=True, source = Microphone(speaker=True,
device_index= device["index"], device_index= device["index"],
sample_rate=int(device["defaultSampleRate"]), sample_rate=int(device["defaultSampleRate"]),
chunk_size=pyaudio.get_sample_size(pyaudio.paInt16), chunk_size=get_sample_size(paInt16),
channels=device["maxInputChannels"] channels=device["maxInputChannels"]
) )
super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout)
self.adjust_for_noise() self.adjust_for_noise()
class BaseEnergyRecorder:
def __init__(self, source):
self.recorder = Recognizer()
self.recorder.energy_threshold = 0
self.recorder.dynamic_energy_threshold = False
self.record_timeout = 0
self.stop = None
if source is None:
raise ValueError("audio source can't be None")
self.source = source
def adjust_for_noise(self):
with self.source:
self.recorder.adjust_for_ambient_noise(self.source)
def record_into_queue(self, energy_queue):
def record_callback(_, energy):
energy_queue.put(energy)
self.stop = self.recorder.listen_energy_in_background(self.source, record_callback)
class SelectedMicEnergyRecorder(BaseEnergyRecorder):
def __init__(self, device):
source=Microphone(
device_index=device['index'],
sample_rate=int(device["defaultSampleRate"]),
)
super().__init__(source=source)
self.adjust_for_noise()
class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder):
def __init__(self, device):
source = Microphone(speaker=True,
device_index= device["index"],
sample_rate=int(device["defaultSampleRate"]),
chunk_size=get_sample_size(paInt16),
channels=device["maxInputChannels"]
)
super().__init__(source=source)
self.adjust_for_noise()

View File

@@ -1,9 +1,9 @@
import io from io import BytesIO
import threading from threading import Event
import wave import wave
import speech_recognition as sr from speech_recognition import Recognizer, AudioData, AudioFile
from datetime import timedelta from datetime import timedelta
import pyaudiowpatch as pyaudio from pyaudiowpatch import get_sample_size, paInt16
PHRASE_TIMEOUT = 3 PHRASE_TIMEOUT = 3
MAX_PHRASES = 10 MAX_PHRASES = 10
@@ -15,8 +15,8 @@ class AudioTranscriber:
self.phrase_timeout = phrase_timeout self.phrase_timeout = phrase_timeout
self.max_phrases = max_phrases self.max_phrases = max_phrases
self.transcript_data = [] self.transcript_data = []
self.transcript_changed_event = threading.Event() self.transcript_changed_event = Event()
self.audio_recognizer = sr.Recognizer() self.audio_recognizer = Recognizer()
self.audio_sources = { self.audio_sources = {
"sample_rate": source.SAMPLE_RATE, "sample_rate": source.SAMPLE_RATE,
"sample_width": source.SAMPLE_WIDTH, "sample_width": source.SAMPLE_WIDTH,
@@ -59,19 +59,18 @@ class AudioTranscriber:
source_info["last_spoken"] = time_spoken source_info["last_spoken"] = time_spoken
def process_mic_data(self): def process_mic_data(self):
audio_data = sr.AudioData(self.audio_sources["last_sample"], self.audio_sources["sample_rate"], self.audio_sources["sample_width"]) audio_data = AudioData(self.audio_sources["last_sample"], self.audio_sources["sample_rate"], self.audio_sources["sample_width"])
return audio_data return audio_data
def process_speaker_data(self): def process_speaker_data(self):
temp_file = io.BytesIO() temp_file = BytesIO()
with wave.open(temp_file, 'wb') as wf: with wave.open(temp_file, 'wb') as wf:
wf.setnchannels(self.audio_sources["channels"]) wf.setnchannels(self.audio_sources["channels"])
p = pyaudio.PyAudio() wf.setsampwidth(get_sample_size(paInt16))
wf.setsampwidth(p.get_sample_size(pyaudio.paInt16))
wf.setframerate(self.audio_sources["sample_rate"]) wf.setframerate(self.audio_sources["sample_rate"])
wf.writeframes(self.audio_sources["last_sample"]) wf.writeframes(self.audio_sources["last_sample"])
temp_file.seek(0) temp_file.seek(0)
with sr.AudioFile(temp_file) as source: with AudioFile(temp_file) as source:
audio = self.audio_recognizer.record(source) audio = self.audio_recognizer.record(source)
return audio return audio

View File

@@ -1,40 +1,43 @@
import pyaudiowpatch as pyaudio from pyaudiowpatch import PyAudio, paWASAPI
def get_input_device_list(): def get_input_device_list():
devices = [] devices = {}
with pyaudio.PyAudio() as p: with PyAudio() as p:
wasapi_info = p.get_host_api_info_by_type(pyaudio.paWASAPI)
for host_index in range(0, p.get_host_api_count()): for host_index in range(0, p.get_host_api_count()):
for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']): host = p.get_host_api_info_by_index(host_index)
for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']):
device = p.get_device_info_by_host_api_device_index(host_index, device_index) device = p.get_device_info_by_host_api_device_index(host_index, device_index)
if device["hostApi"] == wasapi_info["index"] and device["maxInputChannels"] > 0 and device["isLoopbackDevice"] is False: if device["maxInputChannels"] > 0 and device["isLoopbackDevice"] is False:
devices.append(device) if host["name"] in devices.keys():
devices[host["name"]].append(device)
else:
devices[host["name"]] = [device]
return devices return devices
def get_output_device_list(): def get_output_device_list():
devices =[] devices =[]
with pyaudio.PyAudio() as p: with PyAudio() as p:
wasapi_info = p.get_host_api_info_by_type(pyaudio.paWASAPI) wasapi_info = p.get_host_api_info_by_type(paWASAPI)
for device in p.get_loopback_device_info_generator(): for device in p.get_loopback_device_info_generator():
if device["hostApi"] == wasapi_info["index"] and device["isLoopbackDevice"] is True: if device["hostApi"] == wasapi_info["index"] and device["isLoopbackDevice"] is True:
devices.append(device) devices.append(device)
return devices return devices
def get_default_input_device(): def get_default_input_device():
with pyaudio.PyAudio() as p: with PyAudio() as p:
wasapi_info = p.get_host_api_info_by_type(pyaudio.paWASAPI) api_info = p.get_default_host_api_info()
defaultInputDevice = wasapi_info["defaultInputDevice"] defaultInputDevice = api_info["defaultInputDevice"]
for host_index in range(0, p.get_host_api_count()): for host_index in range(0, p.get_host_api_count()):
host = p.get_host_api_info_by_index(host_index)
for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']): for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']):
device = p.get_device_info_by_host_api_device_index(host_index, device_index) device = p.get_device_info_by_host_api_device_index(host_index, device_index)
if device["index"] == defaultInputDevice: if device["index"] == defaultInputDevice:
default_device = device return {"host":host, "device": device}
return default_device
def get_default_output_device(): def get_default_output_device():
with pyaudio.PyAudio() as p: with PyAudio() as p:
wasapi_info = p.get_host_api_info_by_type(pyaudio.paWASAPI) wasapi_info = p.get_host_api_info_by_type(paWASAPI)
defaultOutputDevice = wasapi_info["defaultOutputDevice"] defaultOutputDevice = wasapi_info["defaultOutputDevice"]
for host_index in range(0, p.get_host_api_count()): for host_index in range(0, p.get_host_api_count()):

325
ctk_scrollable_dropdown.py Normal file
View File

@@ -0,0 +1,325 @@
"""
CustomTkinter Scrollable Dropdown Menu
Author: Akash Bora
License: MIT
This is a custom dropdown menu for customtkinter.
Homepage: https://github.com/Akascape/CTkScrollableDropdown
Advanced Scrollable Dropdown class for customtkinter widgets
Author: Akash Bora
"""
import customtkinter
import sys
import time
class CTkScrollableDropdown(customtkinter.CTkToplevel):
def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None,
fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None,
scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[],
command=None, image_values=[], alpha: float = 0.97, frame_corner_radius=20, double_click=False,
resize=True, frame_border_color=None, text_color=None, autocomplete=False, **button_kwargs):
super().__init__(takefocus=1)
self.transient(self.master)
self.alpha = alpha
self.attach = attach
self.corner = frame_corner_radius
self.padding = 0
self.focus_something = False
self.disable = True
if sys.platform.startswith("win"):
self.after(100, lambda: self.overrideredirect(True))
self.transparent_color = self._apply_appearance_mode(self._fg_color)
self.attributes("-transparentcolor", self.transparent_color)
elif sys.platform.startswith("darwin"):
self.overrideredirect(True)
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
self.focus_something = True
else:
self.overrideredirect(True)
self.transparent_color = '#000001'
self.corner = 0
self.padding = 18
self.withdraw()
self.hide = True
self.attach.bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind("<Triple-Button-1>", lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind("<Button-3>", lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind("<Button-2>", lambda e: self._withdraw() if not self.disable else None, add="+")
self.attributes('-alpha', 0)
self.disable = False
self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color
self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
if scrollbar is False:
self.scroll_button_color = self.fg_color
self.scroll_hover_color = self.fg_color
self.frame = customtkinter.CTkScrollableFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color,
scrollbar_button_hover_color=self.scroll_hover_color,
corner_radius=self.corner, border_width=frame_border_width,
scrollbar_button_color=self.scroll_button_color,
border_color=self.frame_border_color)
self.frame._scrollbar.grid_configure(padx=3)
self.frame.pack(expand=True, fill="both")
self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1)
self.no_match = customtkinter.CTkLabel(self.frame, text="No Match")
self.height = height
self.height_new = height
self.width = width
self.command = command
self.fade = False
self.resize = resize
self.autocomplete = autocomplete
self.var_update = customtkinter.StringVar()
self.appear = False
if justify.lower()=="left":
self.justify = "w"
elif justify.lower()=="right":
self.justify = "e"
else:
self.justify = "c"
self.button_height = button_height
self.values = values
self.button_num = len(self.values)
self.image_values = None if len(image_values)!=len(self.values) else image_values
self.resizable(width=False, height=False)
self._init_buttons(**button_kwargs)
# Add binding for different ctk widgets
if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach.bind('<Double-Button-1>', lambda e: self._iconify(), add="+")
else:
self.attach.bind('<Button-1>', lambda e: self._iconify(), add="+")
if self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach._canvas.tag_bind("right_parts", "<Button-1>", lambda e: self._iconify())
self.attach._canvas.tag_bind("dropdown_arrow", "<Button-1>", lambda e: self._iconify())
if self.command is None:
self.command = self.attach.set
if self.attach.winfo_name().startswith("!ctkoptionmenu"):
self.attach._canvas.bind("<Button-1>", lambda e: self._iconify())
self.attach._text_label.bind("<Button-1>", lambda e: self._iconify())
if self.command is None:
self.command = self.attach.set
self.update_idletasks()
self.x = x
self.y = y
if self.autocomplete:
self.bind_autocomplete()
# self.deiconify()
self.withdraw()
self.attributes("-alpha", self.alpha)
def _withdraw(self):
if self.hide is False: self.withdraw()
self.hide = True
def _update(self, a, b, c):
self.live_update(self.attach._entry.get())
def bind_autocomplete(self, ):
def appear(x):
self.appear = True
if self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach._entry.configure(textvariable=self.var_update)
self.attach._entry.bind("<Key>", appear)
self.attach.set(self.values[0])
self.var_update.trace_add('write', self._update)
if self.attach.winfo_name().startswith("!ctkentry"):
self.attach.configure(textvariable=self.var_update)
self.attach.bind("<Key>", appear)
self.var_update.trace_add('write', self._update)
def fade_out(self):
for i in range(100,0,-10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/100)
def fade_in(self):
for i in range(0,100,10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/100)
def _init_buttons(self, **button_kwargs):
self.i = 0
self.widgets = {}
for row in self.values:
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
text=row,
height=self.button_height,
fg_color=self.button_color,
text_color=self.text_color,
image=self.image_values[i] if self.image_values is not None else None,
anchor=self.justify,
command=lambda k=row: self._attach_key_press(k), **button_kwargs)
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
self.i+=1
self.hide = False
def destroy_popup(self):
self.destroy()
self.disable = True
def place_dropdown(self):
self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x + self.attach.winfo_rootx()
self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty()
self.width_new = self.attach.winfo_width() if self.width is None else self.width
if self.resize:
if self.button_num==1:
self.height_new = self.button_height * self.button_num + 45
else:
self.height_new = self.button_height * self.button_num + 35
if self.height_new>self.height:
self.height_new = self.height
self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new,
self.x_pos, self.y_pos))
self.fade_in()
self.attributes('-alpha', self.alpha)
def _iconify(self):
if self.disable: return
if self.hide:
self._deiconify()
self.place_dropdown()
if self.focus_something:
self.dummy_entry.pack()
self.dummy_entry.focus_set()
self.after(100, self.dummy_entry.pack_forget)
self.hide = False
self.focus_set()
self.focus()
else:
self.withdraw()
self.hide = True
def _attach_key_press(self, k):
self.fade = True
if self.command:
self.command(k)
self.fade = False
self.fade_out()
self.withdraw()
self.hide = True
def live_update(self, string=None):
if not self.appear: return
if self.disable: return
if self.fade: return
if string:
self._deiconify()
i=1
for key in self.widgets.keys():
s = self.widgets[key].cget("text")
if not s.startswith(string):
self.widgets[key].pack_forget()
else:
self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0))
i+=1
if i==1:
self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0))
else:
self.no_match.pack_forget()
self.button_num = i
self.place_dropdown()
else:
self.no_match.pack_forget()
self.button_num = len(self.values)
for key in self.widgets.keys():
self.widgets[key].destroy()
self._init_buttons()
self.place_dropdown()
self.frame._parent_canvas.yview_moveto(0.0)
self.appear = False
def insert(self, value, **kwargs):
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
text=value,
height=self.button_height,
fg_color=self.button_color,
text_color=self.text_color,
anchor=self.justify,
command=lambda k=value: self._attach_key_press(k), **kwargs)
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
self.i+=1
self.values.append(value)
def _deiconify(self):
if len(self.values)>0:
self.deiconify()
def popup(self, x=None, y=None):
self.x = x
self.y = y
self.hide = True
self._iconify()
def configure(self, **kwargs):
if "height" in kwargs:
self.height = kwargs.pop("height")
self.height_new = self.height
if "alpha" in kwargs:
self.alpha = kwargs.pop("alpha")
if "width" in kwargs:
self.width = kwargs.pop("width")
if "fg_color" in kwargs:
self.frame.configure(fg_color=kwargs.pop("fg_color"))
if "values" in kwargs:
self.values = kwargs.pop("values")
self.image_values = None
for key in self.widgets.keys():
self.widgets[key].destroy()
self._init_buttons()
if "image_values" in kwargs:
self.image_values = kwargs.pop("image_values")
self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values
if self.image_values is not None:
i=0
for key in self.widgets.keys():
self.widgets[key].configure(image=self.image_values[i])
i+=1
if "button_color" in kwargs:
for key in self.widgets.keys():
self.widgets[key].configure(fg_color=kwargs.pop("button_color"))
for key in self.widgets.keys():
self.widgets[key].configure(**kwargs)

View File

@@ -154,128 +154,134 @@ translation_lang["DeepL(auth)"] = {
"Chinese":"zh" "Chinese":"zh"
} }
translation_lang["Google(web)"] = { translation_lang["Google(web)"] = {
"japanese":"ja", "Japanese":"ja",
"english":"en", "English":"en",
"chinese":"zh", "Chinese":"zh",
"arabic":"ar", "Arabic":"ar",
"russian":"ru", "Russian":"ru",
"french":"fr", "French":"fr",
"german":"de", "German":"de",
"spanish":"es", "Spanish":"es",
"portuguese":"pt", "Portuguese":"pt",
"italian":"it", "Italian":"it",
"korean":"ko", "Korean":"ko",
"greek":"el", "Greek":"el",
"dutch":"nl", "Dutch":"nl",
"hindi":"hi", "Hindi":"hi",
"turkish":"tr", "Turkish":"tr",
"malay":"ms", "Malay":"ms",
"thai":"th", "Thai":"th",
"vietnamese":"vi", "Vietnamese":"vi",
"indonesian":"id", "Indonesian":"id",
"hebrew":"he", "Hebrew":"he",
"polish":"pl", "Polish":"pl",
"mongolian":"mn", "Mongolian":"mn",
"czech":"cs", "Czech":"cs",
"hungarian":"hu", "Hungarian":"hu",
"estonian":"et", "Estonian":"et",
"bulgarian":"bg", "Bulgarian":"bg",
"danish":"da", "Danish":"da",
"finnish":"fi", "Finnish":"fi",
"romanian":"ro", "Romanian":"ro",
"swedish":"sv", "Swedish":"sv",
"slovenian":"sl", "Slovenian":"sl",
"persian/farsi":"fa", "Persian/Farsi":"fa",
"bosnian":"bs", "Bosnian":"bs",
"serbian":"sr", "Serbian":"sr",
"filipino":"tl", "Filipino":"tl",
"haitiancreole":"ht", "Haitiancreole":"ht",
"catalan":"ca", "Catalan":"ca",
"croatian":"hr", "Croatian":"hr",
"latvian":"lv", "Latvian":"lv",
"lithuanian":"lt", "Lithuanian":"lt",
"urdu":"ur", "Urdu":"ur",
"ukrainian":"uk", "Ukrainian":"uk",
"welsh":"cy", "Welsh":"cy",
"swahili":"sw", "Swahili":"sw",
"samoan":"sm", "Samoan":"sm",
"slovak":"sk", "Slovak":"sk",
"afrikaans":"af", "Afrikaans":"af",
"norwegian":"no", "Norwegian":"no",
"bengali":"bn", "Bengali":"bn",
"malagasy":"mg", "Malagasy":"mg",
"maltese":"mt", "Maltese":"mt",
"gujarati":"gu", "Gujarati":"gu",
"tamil":"ta", "Tamil":"ta",
"telugu":"te", "Telugu":"te",
"punjabi":"pa", "Punjabi":"pa",
"amharic":"am", "Amharic":"am",
"azerbaijani":"az", "Azerbaijani":"az",
"belarusian":"be", "Belarusian":"be",
"cebuano":"ceb", "Cebuano":"ceb",
"esperanto":"eo", "Esperanto":"eo",
"basque":"eu", "Basque":"eu",
"irish":"ga" "Irish":"ga"
} }
translation_lang["Bing(web)"] = { translation_lang["Bing(web)"] = {
"japanese":"ja", "Japanese":"ja",
"english":"en", "English":"en",
"chinese":"zh", "Chinese":"zh",
"arabic":"ar", "Arabic":"ar",
"russian":"ru", "Russian":"ru",
"french":"fr", "French":"fr",
"german":"de", "German":"de",
"spanish":"es", "Spanish":"es",
"portuguese":"pt", "Portuguese":"pt",
"italian":"it", "Italian":"it",
"korean":"ko", "Korean":"ko",
"greek":"el", "Greek":"el",
"dutch":"nl", "Dutch":"nl",
"hindi":"hi", "Hindi":"hi",
"turkish":"tr", "Turkish":"tr",
"malay":"ms", "Malay":"ms",
"thai":"th", "Thai":"th",
"vietnamese":"vi", "Vietnamese":"vi",
"indonesian":"id", "Indonesian":"id",
"hebrew":"he", "Hebrew":"he",
"polish":"pl", "Polish":"pl",
"czech":"cs", "Czech":"cs",
"hungarian":"hu", "Hungarian":"hu",
"estonian":"et", "Estonian":"et",
"bulgarian":"bg", "Bulgarian":"bg",
"danish":"da", "Danish":"da",
"finnish":"fi", "Finnish":"fi",
"romanian":"ro", "Romanian":"ro",
"swedish":"sv", "Swedish":"sv",
"slovenian":"sl", "Slovenian":"sl",
"persian/farsi":"fa", "Persian/Farsi":"fa",
"bosnian":"bs", "Bosnian":"bs",
"serbian":"sr", "Serbian":"sr",
"fijian":"fj", "Fijian":"fj",
"filipino":"tl", "Filipino":"tl",
"haitiancreole":"ht", "Haitiancreole":"ht",
"catalan":"ca", "Catalan":"ca",
"croatian":"hr", "Croatian":"hr",
"latvian":"lv", "Latvian":"lv",
"lithuanian":"lt", "Lithuanian":"lt",
"urdu":"ur", "Urdu":"ur",
"ukrainian":"uk", "Ukrainian":"uk",
"welsh":"cy", "Welsh":"cy",
"tahiti":"ty", "Tahiti":"ty",
"tongan":"to", "Tongan":"to",
"swahili":"sw", "Swahili":"sw",
"samoan":"sm", "Samoan":"sm",
"slovak":"sk", "Slovak":"sk",
"afrikaans":"af", "Afrikaans":"af",
"norwegian":"no", "Norwegian":"no",
"bengali":"bn", "Bengali":"bn",
"malagasy":"mg", "Malagasy":"mg",
"maltese":"mt", "Maltese":"mt",
"queretaro otomi":"otq", "Queretaro otomi":"otq",
"klingon/tlhingan hol":"tlh", "Klingon/tlhingan Hol":"tlh",
"gujarati":"gu", "Gujarati":"gu",
"tamil":"ta", "Tamil":"ta",
"telugu":"te", "Telugu":"te",
"punjabi":"pa", "Punjabi":"pa",
"irish":"ga" "Irish":"ga"
}
selectable_languages = {
"en": "English",
"ja": "日本語",
# 新しい言語とキーを追加する場合はここに追記してください
} }

128
locales.yml Normal file
View File

@@ -0,0 +1,128 @@
en:
# main window
checkbox_translation: "Translation"
checkbox_transcription_send: "Voice2chatbox"
checkbox_transcription_receive: "Speaker2log"
checkbox_foreground: "Foreground"
# main tabview
main_tab_title_log: "Log"
main_tab_title_send: "Send"
main_tab_title_receive: "Receive"
main_tab_title_system: "System"
# configure window
# config tabview
config_tab_title_ui: "UI"
config_tab_title_translation: "Translation"
config_tab_title_transcription: "Transcription"
config_tab_title_parameter: "Parameter"
config_tab_title_others: "Others"
# tab UI
label_transparency: "Transparency"
label_appearance_theme: "Appearance Theme"
label_ui_scaling: "UI Scaling"
label_font_family: "Font Family"
label_ui_language: "UI Language"
# tab Translation
label_translation_translator: "Select Translator"
label_translation_input_language: "Send Language"
label_translation_output_language: "Receive Language"
# tab Transcription
label_input_mic_host: "Input Mic Host"
label_input_mic_device: "Input Mic Device"
label_input_mic_voice_language: "Input Mic Voice Language"
label_input_mic_energy_threshold: "Input Mic Energy Threshold"
checkbox_input_mic_threshold_check: "Check threshold point"
label_input_mic_dynamic_energy_threshold: "Input Mic Dynamic Energy Threshold"
label_input_mic_record_timeout: "Input Mic Record Timeout"
label_input_mic_phrase_timeout: "Input Mic Phrase Timeout"
label_input_mic_max_phrases: "Input Mic Max Phrases"
label_input_mic_word_filter: "Input Mic Word Filter"
label_input_speaker_device: "Input Speaker Device"
label_input_speaker_voice_language: "Input Speaker Voice Language"
label_input_speaker_energy_threshold: "Input Speaker Energy Threshold"
checkbox_input_speaker_threshold_check: "Check threshold point"
label_input_speaker_dynamic_energy_threshold: "Input Speaker Dynamic Energy Threshold"
label_input_speaker_record_timeout: "Input Speaker Record Timeout"
label_input_speaker_phrase_timeout: "Input Speaker Phrase Timeout"
label_input_speaker_max_phrases: "Input Speaker Max Phrases"
# tab Parameter
label_ip_address: "OSC IP address"
label_port: "OSC Port"
label_authkey: "DeepL Auth Key"
label_message_format: "Message Format"
# tab Others
label_checkbox_auto_clear_chatbox: "Auto clear chat box"
ja:
# main window
checkbox_translation: "翻訳"
checkbox_transcription_send: "マイク->チャットボックス"
checkbox_transcription_receive: "スピーカー->ログ"
checkbox_foreground: "最前面表示"
# main tabview
main_tab_title_log: "ログ"
main_tab_title_send: "送信"
main_tab_title_receive: "受信"
main_tab_title_system: "システム"
# configure window
# config tabview
config_tab_title_ui: "UI"
config_tab_title_translation: "翻訳方法"
config_tab_title_transcription: "音声認識"
config_tab_title_parameter: "パラメーター"
config_tab_title_others: "その他"
# tab UI
label_transparency: "透過度"
label_appearance_theme: "外観テーマを選択"
label_ui_scaling: "UIの拡大縮小"
label_font_family: "使用フォントの変更"
label_ui_language: "UI 言語"
# tab Translation
label_translation_translator: "翻訳エンジンの選択"
label_translation_input_language: "送信言語-->翻訳言語"
label_translation_output_language: "受信言語-->翻訳言語"
# tab Transcription
label_input_mic_host: "マイク入力ホスト"
label_input_mic_device: "マイク入力デバイス"
label_input_mic_voice_language: "マイクで話す言語"
label_input_mic_energy_threshold: "音声取得のしきい値"
checkbox_input_mic_threshold_check: "音声取得のしきい値の視覚化"
label_input_mic_dynamic_energy_threshold: "音声取得のしきい値の自動調整"
label_input_mic_record_timeout: "マイク音声の区切りの無音時間"
label_input_mic_phrase_timeout: "文字起こしする音声時間の上限"
label_input_mic_max_phrases: "保留する単語の上限(マイク)"
label_input_mic_word_filter: "ワードフィルタ"
label_input_speaker_device: "スピーカー(聞き取りたいデバイス)"
label_input_speaker_voice_language: "聞き取る音声の言語"
label_input_speaker_energy_threshold: "音声取得のしきい値"
checkbox_input_speaker_threshold_check: "音声取得のしきい値の視覚化"
label_input_speaker_dynamic_energy_threshold: "音声取得のしきい値の自動調整"
label_input_speaker_record_timeout: "スピーカー音声の区切りの無音時間"
label_input_speaker_phrase_timeout: "文字起こしする音声時間の上限"
label_input_speaker_max_phrases: "保留する単語の上限(スピーカー)"
# tab Parameter
# label_ip_address: ""
# label_port: ""
# label_authkey: ""
label_message_format: "送信するメッセージのフォーマット"
# tab Others
label_checkbox_auto_clear_chatbox: "送信後はチャットボックスを空にする"

View File

@@ -3,3 +3,5 @@ PyAudioWPatch
python-osc python-osc
customtkinter customtkinter
deepl deepl
flashtext
pyyaml

View File

@@ -1,13 +1,13 @@
import deepl from deepl import Translator as deepl_Translator
import deepl_translate from deepl_translate import translate as deepl_web_Translator
import translators as ts from translators import translate_text as other_web_Translator
import languages from languages import translators, translation_lang
# Translator # Translator
class Translator(): class Translator():
def __init__(self): def __init__(self):
self.translator_status = {} self.translator_status = {}
for translator in languages.translators: for translator in translators:
self.translator_status[translator] = False self.translator_status[translator] = False
self.deepl_client = None self.deepl_client = None
@@ -18,7 +18,7 @@ class Translator():
self.translator_status["DeepL(web)"] = True self.translator_status["DeepL(web)"] = True
result = True result = True
elif translator_name == "DeepL(auth)": elif translator_name == "DeepL(auth)":
self.deepl_client = deepl.Translator(authkey) self.deepl_client = deepl_Translator(authkey)
self.deepl_client.translate_text(" ", target_lang="EN-US") self.deepl_client.translate_text(" ", target_lang="EN-US")
self.translator_status["DeepL(auth)"] = True self.translator_status["DeepL(auth)"] = True
result = True result = True
@@ -35,10 +35,10 @@ class Translator():
def translate(self, translator_name, source_language, target_language, message): def translate(self, translator_name, source_language, target_language, message):
result = "" result = ""
try: try:
source_language=languages.translation_lang[translator_name][source_language] source_language=translation_lang[translator_name][source_language]
target_language=languages.translation_lang[translator_name][target_language] target_language=translation_lang[translator_name][target_language]
if translator_name == "DeepL(web)": if translator_name == "DeepL(web)":
result = deepl_translate.translate( result = deepl_web_Translator(
source_language=source_language, source_language=source_language,
target_language=target_language, target_language=target_language,
text=message text=message
@@ -50,14 +50,14 @@ class Translator():
target_lang=target_language, target_lang=target_language,
).text ).text
elif translator_name == "Google(web)": elif translator_name == "Google(web)":
result = ts.translate_text( result = other_web_Translator(
query_text=message, query_text=message,
translator="google", translator="google",
from_language=source_language, from_language=source_language,
to_language=target_language, to_language=target_language,
) )
elif translator_name == "Bing(web)": elif translator_name == "Bing(web)":
result = ts.translate_text( result = other_web_Translator(
query_text=message, query_text=message,
translator="bing", translator="bing",
from_language=source_language, from_language=source_language,

105
utils.py
View File

@@ -1,16 +1,18 @@
import json from json import load, dump
import datetime from os import path as os_path
import threading import yaml
from datetime import datetime
from threading import Thread, Event
def save_json(path, key, value): def save_json(path, key, value):
with open(path, "r") as fp: with open(path, "r") as fp:
json_data = json.load(fp) json_data = load(fp)
json_data[key] = value json_data[key] = value
with open(path, "w") as fp: with open(path, "w") as fp:
json.dump(json_data, fp, indent=4) dump(json_data, fp, indent=4)
def print_textbox(textbox, message, tags=None): def print_textbox(textbox, message, tags=None):
now = datetime.datetime.now() now = datetime.now()
now = now.strftime('%H:%M:%S') now = now.strftime('%H:%M:%S')
textbox.tag_config("ERROR", foreground="#FF0000") textbox.tag_config("ERROR", foreground="#FF0000")
@@ -25,11 +27,11 @@ def print_textbox(textbox, message, tags=None):
textbox.configure(state='disabled') textbox.configure(state='disabled')
textbox.see("end") textbox.see("end")
class thread_fnc(threading.Thread): class thread_fnc(Thread):
def __init__(self, fnc, daemon=True, *args, **kwargs): def __init__(self, fnc, daemon=True, *args, **kwargs):
super(thread_fnc, self).__init__(daemon=daemon, *args, **kwargs) super(thread_fnc, self).__init__(daemon=daemon, *args, **kwargs)
self.fnc = fnc self.fnc = fnc
self._stop = threading.Event() self._stop = Event()
def stop(self): def stop(self):
self._stop.set() self._stop.set()
def stopped(self): def stopped(self):
@@ -39,3 +41,90 @@ class thread_fnc(threading.Thread):
if self.stopped(): if self.stopped():
return return
self.fnc(*self._args, **self._kwargs) self.fnc(*self._args, **self._kwargs)
def get_localized_text(language):
file_path = os_path.join(os_path.dirname(__file__), "locales.yml")
with open(file_path, encoding="utf-8") as file:
languages_yaml_data = yaml.safe_load(file)
default_language = "en"
if language in languages_yaml_data:
localized_text = languages_yaml_data[language]
if default_language in languages_yaml_data:
default_text = languages_yaml_data[default_language]
merged_text = {**default_text, **localized_text}
return merged_text
else:
return localized_text
else:
return None
def get_key_by_value(dictionary, value):
for key, val in dictionary.items():
if val == value:
return key
return None
def widget_config_window_label_setter(self, language_yaml_data):
widget_names = [
# tab UI
"label_transparency",
"label_appearance_theme",
"label_ui_scaling",
"label_font_family",
"label_ui_language",
# tab Translation
"label_translation_translator",
"label_translation_input_language",
"label_translation_output_language",
# tab Transcription
"label_input_mic_host",
"label_input_mic_device",
"label_input_mic_voice_language",
"label_input_mic_energy_threshold",
"checkbox_input_mic_threshold_check",
"label_input_mic_dynamic_energy_threshold",
"label_input_mic_record_timeout",
"label_input_mic_phrase_timeout",
"label_input_mic_max_phrases",
"label_input_mic_word_filter",
"label_input_speaker_device",
"label_input_speaker_voice_language",
"label_input_speaker_energy_threshold",
"checkbox_input_speaker_threshold_check",
"label_input_speaker_dynamic_energy_threshold",
"label_input_speaker_record_timeout",
"label_input_speaker_phrase_timeout",
"label_input_speaker_max_phrases",
# tab Parameter
"label_ip_address",
"label_port",
"label_authkey",
"label_message_format",
# tab Others
"label_checkbox_auto_clear_chatbox"
]
for name in widget_names:
widget = getattr(self, name)
text_value = language_yaml_data.get(name)
if widget is not None and text_value is not None:
widget.configure(text=text_value + ":")
def widget_main_window_label_setter(self, language_yaml_data):
widget_names = [
"checkbox_translation",
"checkbox_transcription_send",
"checkbox_transcription_receive",
"checkbox_foreground",
]
for name in widget_names:
widget = getattr(self, name)
text_value = language_yaml_data.get(name)
if widget is not None and text_value is not None:
widget.configure(text=text_value)

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import os import os
import customtkinter from customtkinter import CTkToplevel, CTkTextbox, CTkFont
class ToplevelWindowInformation(customtkinter.CTkToplevel): class ToplevelWindowInformation(CTkToplevel):
def __init__(self, parent, *args, **kwargs): def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.parent = parent self.parent = parent
@@ -13,12 +13,12 @@ class ToplevelWindowInformation(customtkinter.CTkToplevel):
self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico"))) self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico")))
self.title("Information") self.title("Information")
# create textbox information # create textbox information
self.textbox_information = customtkinter.CTkTextbox( self.textbox_information = CTkTextbox(
self, self,
font=customtkinter.CTkFont(family=self.parent.FONT_FAMILY) font=CTkFont(family=self.parent.FONT_FAMILY)
) )
self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew")
textbox_information_message = """VRCT(v1.2) textbox_information_message = """VRCT(v1.3)
# 概要 # 概要
VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。
@@ -32,7 +32,7 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ
(任意) (任意)
1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する
2. ギアアイコンのボタンでconfigウィンドウを開く 2. ギアアイコンのボタンでconfigウィンドウを開く
3. ParameterタブのDeepL Auth Keyに認証キーを記載し、フロッピーアイコンのボタンを押す 3. ParameterタブのDeepL Auth Keyに認証キーを記載
4. configウィンドウを閉じる 4. configウィンドウを閉じる
通常使用時 通常使用時
@@ -61,22 +61,29 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ
Appearance Theme: ウィンドウテーマを選択 Appearance Theme: ウィンドウテーマを選択
UI Scaling: UIサイズを調整 UI Scaling: UIサイズを調整
Font Family: 表示フォントを選択 Font Family: 表示フォントを選択
(New!) UI Language: UIの表示言語を選択
Translationタブ Translationタブ
Select Translator: 翻訳エンジンの変更 Select Translator: 翻訳エンジンの変更
Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択
Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択
Transcriptionタブ Transcriptionタブ
(New!) Input Mic Host: マイクのホストAPIを選択
Input Mic Device: マイクを選択 Input Mic Device: マイクを選択
Input Mic Voice Language: 入力する音声の言語 Input Mic Voice Language: 入力する音声の言語
Input Mic Energy Threshold: 音声取得のしきい値 Input Mic Energy Threshold: 音声取得のしきい値
(New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化
Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整
Input Mic Record Timeout: 音声の区切りの無音時間 Input Mic Record Timeout: 音声の区切りの無音時間
Input Mic Phase Timeout: 文字起こしする音声時間の上限
Input Mic Max Phrases: 保留する単語の上限 Input Mic Max Phrases: 保留する単語の上限
(New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC)
Input Speaker Device: スピーカーを選択 Input Speaker Device: スピーカーを選択
Input Speaker Voice Language: 受信する音声の言語 Input Speaker Voice Language: 受信する音声の言語
Input Speaker Energy Threshold: 音声取得のしきい値 Input Speaker Energy Threshold: 音声取得のしきい値
(New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化
Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整
Input Speaker Record Timeout: 音声の区切りの無音時間 Input Speaker Record Timeout: 音声の区切りの無音時間
Input Speaker Phase Timeout: 文字起こしする音声時間の上限
Input Speaker Max Phrases: 保留する単語の上限 Input Speaker Max Phrases: 保留する単語の上限
Parameterタブ Parameterタブ
OSC IP address: 変更不要 OSC IP address: 変更不要
@@ -86,6 +93,8 @@ VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツ
[message]がメッセージボックスに記入したメッセージに置換される [message]がメッセージボックスに記入したメッセージに置換される
[translation]が翻訳されたメッセージに置換される [translation]が翻訳されたメッセージに置換される
初期フォーマット:"[message]([translation])" 初期フォーマット:"[message]([translation])"
Othersタブ
(New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする
設定の初期化 設定の初期化
config.jsonを削除 config.jsonを削除
@@ -118,6 +127,17 @@ https://twitter.com/misya_ai
- いくつかのバクを修正 - いくつかのバクを修正
- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 - 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更
- 文字起こしの処理の軽量化 - 文字起こしの処理の軽量化
[2023-07-05: v1.2]
- 文字起こし精度の向上
[2023-07-21: v1.3]
- UIの表示言語を日本語/英語を選択できる機能を追加
- Energy Thresholdの視覚化機能を追加
- 文字起こしの誤認識対策のため、Word Filterを追加
- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加
- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加
- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更
- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更
- 半角入力時に一部の文字が書き込めないバグを修正
# 注意事項 # 注意事項
再配布とかはやめてね 再配布とかはやめてね