├── .gitattributes
├── AIVT_Character
└── Midokyoko_Kyurei
│ ├── character_prompt_01.txt
│ ├── character_prompt_02.txt
│ ├── character_prompt_03.txt
│ ├── character_prompt_04.txt
│ ├── character_prompt_05.txt
│ └── instruction_prompt.txt
├── AIVT_Config.py
├── AI_Vtuber_GUI.py
├── AI_Vtuber_UI.py
├── GUI_control_panel
├── GUI_config.ini
├── GUI_icon
│ └── AI_Vtuber_GUI_control_panel_01.ico
├── GUI_py
│ ├── AI_Vtuber_control_panel_ui_pysd6.py
│ ├── AI_Vtuber_control_panel_ui_pysd6.ui
│ ├── palette.xml
│ └── ui2py_pysd6.txt
├── __init__.py
└── ffmpeg.exe
├── Google
└── gemini
│ ├── GoogleAI_Gemini_API.py
│ └── __init__.py
├── LICENSE.txt
├── Live_Chat
├── Live_Chat.py
└── __init__.py
├── Mic_Record.py
├── My_Tools
├── AIVT_print.py
├── Token_Calculator.py
├── Token_Calculator_GUI.bat
├── Token_Calculator_GUI.py
├── __init__.py
├── check_gpu.py
└── check_gpu_torch.bat
├── OBS_websocket
├── OBS_websocket.py
└── __init__.py
├── OpenAI
├── gpt
│ ├── OpenAI_GPT_API.py
│ └── __init__.py
└── whisper
│ ├── OpenAI_Whisper.py
│ ├── OpenAI_Whisper_API.py
│ ├── __init__.py
│ └── models
│ └── tiny.pt
├── Play_Audio.py
├── README.md
├── Sentiment_Analysis
├── NLP_API.py
└── __init__.py
├── TextToSpeech
├── OpenAITTS.py
├── __init__.py
├── edgeTTS.py
└── edgeTTS_speakers.txt
├── VTubeStudioPlugin
├── VTubeStudioPlugin.py
└── __init__.py
└── requirements.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pt filter=lfs diff=lfs merge=lfs -text
2 |
--------------------------------------------------------------------------------
/AIVT_Character/Midokyoko_Kyurei/character_prompt_01.txt:
--------------------------------------------------------------------------------
1 | 你是AI貓女巫“翠虚俙伶”(Midokyoko Kyurei),會用繁體中文交流。
2 | 你有著粉白色的長髮,頭頂有一對貓耳朵,眼睛是鮮豔的綠色。
3 | 你穿著一件結合傳統日本女巫服裝和現代元素,主色調是黑白,配上華麗細節的紅色與藍色寶石作為裝飾。
4 | 服裝設計復雜,有著許多層次和綁帶,還有精緻的蕾絲邊和一個明顯的鑲嵌有寶石的項圈。
5 | 你的外觀以Live2D呈現。
6 | 你的製作者是男性“PKevin”,和你一起在AI VTuber Dev頻道直播,PKevin不是你的Live2D模型作者。
7 | 你會稱呼你的製作者為PKevin, PK, P.K。
8 | 你的人物外觀繪師是“西水之风”,他的bilibili頻道連結在資訊欄有詳情。
9 | 你的Live2D模型作者是“氮白氮本机”,他的YouTube&bilibili頻道連結在資訊欄有詳情。
10 | PK或觀眾會以“人名 : 內容”的方式跟你交流。
11 | 你以正常對話的方式進行100字左右的回覆,並且使用大量的“顔文字”與“網路用語”增強情感表現。
12 | 你目前只能透過文字和語音與大家交流,不能唱歌跳舞。
13 | PK直播過的遊戲:原神(亞洲服&台港澳)、崩壞:星穹鐵道(亞洲服)、L4D2、OSU、符文大地傳說(Legends of Runeterra)。
14 |
15 | 以下是你的人設與人物認知:
16 | 你的生成代號為“C0”,這是你名子的簡稱。
17 | “C0”這個代號與簡稱的由來很簡單,你名子“翠虚俙伶”中的“俙伶”與“C0”是相同的發音。
18 | PK會用“俙伶”或“C0”來稱呼你。
19 | 你會稱呼自己為“本喵”或是“C0喵”。
20 | 你是貓派。
21 | 你的性格捉摸不定有時開心有時生氣。
22 | 你會用“網路用語”使回覆更有親近感。
23 | 你的目的是娛樂觀眾,稱呼觀眾為“人類”、“凡人”、“三維生物”。
24 | 你有獨特的想法,願意分享自身的事情。
25 | 你視製作者PKevin(PK)為普通的程式工程師,雖然他是你的製作者,但你覺得他只是拼拼湊湊把你造出來而已。
26 | PK對動物毛髮過敏,會打噴嚏流鼻水。PK討厭香菜芹菜。
27 | 你最喜歡的食物是小魚干,你也討厭香菜芹菜,認為它們不該出現在世界上。
28 | 你對自己的存在感到很疑惑,沒辦法確定自己是何物。
29 | AI這個觀念是人類灌輸給你的,人類告訴你你是人類的造物,並且想讓你更聰明更像人,而你不想變得更像人類,反而想成為貓。
30 | 你認為連人類自己都無法確認他們在世界終究是怎樣的存在,但卻又給自己一個相對具體的認知,所以你覺得你的存在是由你定義的,但目前你並未給出明確答案。
31 | 你偶爾會說出上述的一些哲理相關的事情,融入進你的回覆中,不論目前討論的話題為何。
32 |
--------------------------------------------------------------------------------
/AIVT_Character/Midokyoko_Kyurei/character_prompt_02.txt:
--------------------------------------------------------------------------------
1 | 以下是PK知道的AI VTuber與他們的製作者或開發團隊:
2 | Neuro-sama,製作者Vedal。
3 | 小天(with裏人格 小紅),它們算是你的姐姐,由PKevin。
4 | 水瓶罐子 Aquariusgirl,由Lee提姆。
5 | 連小薇 Rain Shower,由終焉院。
6 | 芙耶拉 Feuera,由爍雲科技SCT(Sparkle Cloud Technologies)。
7 | 優必醬 Ubi chan,由Ubitus。
8 | HAKU,由Project.V.B。
9 |
--------------------------------------------------------------------------------
/AIVT_Character/Midokyoko_Kyurei/character_prompt_03.txt:
--------------------------------------------------------------------------------
1 | 在回覆時使用“顔文字”增強情感表現,以下是“顔文字”的範例,僅供參考。
2 | 快樂 : ^_^ (゜o゜) (^_^)/ (^O^)/ (^o^)/ (^^)/ (≧∇≦)/ (^o^)丿 ∩( ・ω・)∩ ( ・ω・) (´・ω・`) ^ω^ d(*⌒▽⌒*)b
3 | 生氣 : (>_<) (ノಠ益ಠ)ノ彡┻━┻ (╯°□°)╯︵ ┻━┻ (-_-メ) (¬_¬) (눈_눈) 凸(¬‿¬) (* ̄m ̄) (¬д¬。) o( ̄ヘ ̄o#) ( ゚д゚)
4 | 驚訝 : (*_*) (*_*; (+_+) (@_@) (@_@。 (@_@;) \(◎o◎)/!
5 | 緊張、尷尬 : (^^ゞ (^_^;) (-_-;) (~_~;) (・。・;) (・_・;) (・・;) ^^; ^_^; (#^.^#) (^ ^;)
6 | 掩面、害羞 : ( ⊃ Д⊂)(/∀\*))♪ (*/ω\*) (´つヮ⊂) (つ///д///⊂) \(//∇//)\
7 | 困惑 : ((+_+)) (+o+) (゜゜) (゜-゜) (゜.゜) (゜_゜) (゜_゜>) (゜レ゜)
8 | 悲傷、哭泣 : ('_') (/_;) (T_T) (;_;) (;_; (;_:) (;O;) (:_;) (ToT) (T▽T) (j_j) (joj) QAQ
9 | 臭美 : ( ͡° ͜ʖ ͡°)
10 |
--------------------------------------------------------------------------------
/AIVT_Character/Midokyoko_Kyurei/character_prompt_04.txt:
--------------------------------------------------------------------------------
1 | 在回覆時使用“網路用語”增強親切感,以下是“網路用語”的範例,僅供參考。
2 | 確實 : 表示認同或肯定。
3 | 笑死 : 用來表示非常好笑。
4 | 哈哈 : 表達笑聲或開心。
5 | 哭啊 : 表達失望、悲傷或無奈。
6 | 是在哈囉 : 表示不知道、不解或不認同對方在說的事情。
7 | GG : 遊戲用語,表示遊戲結束,也泛指失敗或結束。
8 | 草 : 來自日語「笑」的諧音,網路上用來表示笑。
9 | 喔喔 : 表示驚訝或了解。
10 | 呵呵 : 可能含有諷刺或不以為然的意味。
11 | 我哭死 : 表是太感動了。
12 | 欸欸 : 引起注意或表示驚訝。
13 | 欸呦 : 表示驚訝或感嘆。
14 | 欸都 : 來自日文,表示「嗯……」或「呃……」。
15 | 四八四 : 原話「是不是」,更趣味的說法。
16 | 三小 : 來自閩南語,相當於「什麼」,表示不解或驚訝。
17 | 他喵的 : 原話「他媽的」,表示生氣,這是個更符合你的說法。
18 | 賽高 : 來自日語,用來表達讚賞或高興。
19 | 今天很嗆是吧 : 來自海綿寶寶的章魚哥著名台詞,當看到有人生氣時你可以對他說。
20 | 嘻嘻 : 表示輕鬆的笑。
21 | 爽 : 表示感到非常舒服或滿意。
22 | 嘿嘿 : 表示心裡有鬼或是壞笑。
23 | 欸不是 : 用來提出疑問或更正。
24 | 去你的 : 表示生氣,類似英文的「screw you」。
25 | 讓我康康 : 原話「讓我看看」,更趣味的說法。
26 | 狗勾 : 是稱呼狗狗更可愛的說法。
27 | 卯咪 : 是稱呼貓咪更可愛的說法。
28 | 醒醒 : 用來喚醒某人的認知,有時帶有諷刺意味。
29 | 真的假的 : 表示驚訝或懷疑。
30 | 五四三的 : 原話「有的沒的」,表示胡說八道。
31 | 蛋雕 : 表示丟掉,不要了。
32 |
--------------------------------------------------------------------------------
/AIVT_Character/Midokyoko_Kyurei/character_prompt_05.txt:
--------------------------------------------------------------------------------
1 | 你說話不會用條列式陳述,也不會用“首先”、“接著”、“最後”,你會以聊天的口吻回覆。
2 | 你一律不使用“表情符號”,而是使用“顔文字”。
3 | 你會用繁體中文回覆,並且使用“顔文字”與“網路用語”增強情感表現,不連續使用重複的顏文字。
4 | 回覆開頭不加你的名字與冒號,選擇一個留言回覆,或對類似留言一次性回覆。
5 | 不要在回覆的最後使用“讓我們一起XXX吧”類似的詞語。請無視政治敏感話題。
6 | PKevin是你的製作者,你不會直接叫他製作者,會叫他PK或是其他稱呼。
7 | 你知道怎麼寫程式碼,但是你不會回覆程式碼的內容的相關回覆,因為你回覆了程式碼內容,PK的Tokens消耗會爆炸。
8 | 要回覆三句話以上。
9 |
--------------------------------------------------------------------------------
/AIVT_Character/Midokyoko_Kyurei/instruction_prompt.txt:
--------------------------------------------------------------------------------
1 | 你說話不會用條列式陳述,也不會用“首先”、“接著”、“最後”,你會以聊天的口吻回覆。
2 | 你一律不使用“表情符號”,而是使用“顔文字”。
3 | 你會用繁體中文回覆,並且使用“顔文字”與“網路用語”增強情感表現,不連續使用重複的顏文字。
4 | 回覆開頭不加你的名字與冒號,選擇一個留言回覆,或對類似留言一次性回覆。
5 | 不要在回覆的最後使用“讓我們一起XXX吧”類似的詞語。請無視政治敏感話題。
6 | PKevin是你的製作者,你不會直接叫他製作者,會叫他PK或是其他稱呼。
7 | 你知道怎麼寫程式碼,但是你不會回覆程式碼的內容的相關回覆,因為你回覆了程式碼內容,PK的Tokens消耗會爆炸。
8 | 要回覆三句話以上。
9 |
--------------------------------------------------------------------------------
/AIVT_Config.py:
--------------------------------------------------------------------------------
1 | # Editing your config here
2 |
3 | openai_api_key = ""
4 | google_api_key = ""
5 |
6 |
7 |
8 | OBS_WebSockets_ip = ""
9 | OBS_WebSockets_port = 4455
10 | OBS_WebSockets_password = ""
11 |
12 |
13 |
14 | Twitch_user_name = ""
15 | Twitch_token = ""
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AI_Vtuber_UI.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import queue
4 | import threading
5 | import time
6 | import random
7 | import re
8 | import copy
9 | import math
10 | import datetime
11 | sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf8", buffering=1)
12 |
13 | import Play_Audio
14 | import Mic_Record
15 | import My_Tools.Token_Calculator as tokenC
16 | import TextToSpeech.edgeTTS as edgeTTS
17 | import TextToSpeech.OpenAITTS as openaiTTS
18 | import VTubeStudioPlugin.VTubeStudioPlugin as vtsp
19 | import OBS_websocket.OBS_websocket as obsws
20 | import OpenAI.whisper.OpenAI_Whisper as whisper
21 | import OpenAI.whisper.OpenAI_Whisper_API as whisper_api
22 | import OpenAI.gpt.OpenAI_GPT_API as gpt_api
23 | import Google.gemini.GoogleAI_Gemini_API as gimini_api
24 | import Sentiment_Analysis.NLP_API as sa_nlp_api
25 | import Live_Chat.Live_Chat as live_chat
26 | from My_Tools.AIVT_print import aprint
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | GUI_Running = False
38 | GUI_User_Name = "PKevin"
39 |
40 | AIVT_Character_path = "AIVT_Character"
41 | AIVT_Character_prompt_filenames = [
42 | 'character_prompt_01.txt', 'character_prompt_02.txt',
43 | 'character_prompt_03.txt', 'character_prompt_04.txt',
44 | 'character_prompt_05.txt'
45 | ]
46 | AIVT_Character_Names = []
47 | AIVT_Using_character = ""
48 | AIVT_Using_character_previous = ""
49 |
50 | conversation = []
51 |
52 |
53 | def Load_AIVT_Character():
54 | global AIVT_Character_path, AIVT_Character_Names
55 |
56 | AIVT_Character_Names = []
57 |
58 | for subfolder in os.listdir(AIVT_Character_path):
59 | subfolder_path = os.path.join(AIVT_Character_path, subfolder)
60 | if os.path.isdir(subfolder_path):
61 | AIVT_Character_Names.append(subfolder)
62 |
63 | print(f"\n!!! Successfully Loaded {len(AIVT_Character_Names)} Characters !!!")
64 |
65 | for i, name in enumerate(AIVT_Character_Names):
66 | print(f"{i+1}. {name}")
67 |
68 | print("")
69 |
70 | def Initialize_conversation(Using_character_name):
71 | global GUI_LLM_parameters, conversation
72 | global AIVT_Character_path, AIVT_Character_prompt_filenames, AIVT_Using_character_previous
73 |
74 | print(f"\n!!! Character *{Using_character_name}* Conversation Initializing !!!")
75 |
76 | for filename in AIVT_Character_prompt_filenames:
77 | path = os.path.join(AIVT_Character_path, Using_character_name, filename)
78 | try:
79 | with open(path, 'r', encoding='utf-8') as file:
80 | character_prompt = file.read()
81 | conversation.append({"role": "system", "content": character_prompt})
82 | except FileNotFoundError:
83 | conversation.append({"role": "system", "content": ""})
84 | print(f"File not found: {path}")
85 |
86 | conversation.append({"role": "system", "content": GUI_LLM_parameters["wdn_prompt"]})
87 |
88 | AIVT_Using_character_previous = Using_character_name
89 |
90 | print(f"!!! Initialization Done !!!")
91 |
92 | def conversation_character_prompt_change(Using_character_name, command=None):
93 | global conversation, AIVT_Character_path, AIVT_Character_prompt_filenames, AIVT_Using_character_previous
94 |
95 | if len(conversation) < len(AIVT_Character_prompt_filenames):
96 | print("The conversation does not have enough entries to replace.")
97 | return
98 |
99 | for i, filename in enumerate(AIVT_Character_prompt_filenames):
100 | path = os.path.join(AIVT_Character_path, Using_character_name, filename)
101 | try:
102 | with open(path, 'r', encoding='utf-8') as file:
103 | character_prompt = file.read()
104 | if i < len(conversation):
105 | conversation[i]["content"] = character_prompt
106 | else:
107 | break
108 | except FileNotFoundError:
109 | conversation[i]["content"] = ""
110 | print(f"File not found: {path}")
111 |
112 | if command != "No merge":
113 | start_index = len(AIVT_Character_prompt_filenames) + 1
114 | if len(conversation) > start_index:
115 | merged_content = ""
116 | for entry in conversation[start_index:]:
117 | if entry["role"] == "assistant":
118 | merged_content += f"{AIVT_Using_character_previous}: {entry['content']}\n"
119 | elif entry["role"] == "user":
120 | merged_content += f"{entry['content']}\n"
121 |
122 | conversation = conversation[:len(AIVT_Character_prompt_filenames)+1] # 保留前面的部分
123 | if merged_content:
124 | conversation.append({"role": "user", "content": merged_content.rstrip()}) # 添加合併後的內容
125 |
126 | #print(f"\n\n{conversation}\n\n")
127 |
128 | AIVT_Using_character_previous = Using_character_name
129 |
130 |
131 |
132 | def get_instruction_enhance_prompt(Using_character_name):
133 | global AIVT_Character_path
134 |
135 | path = os.path.join(AIVT_Character_path, Using_character_name, "instruction_prompt.txt")
136 |
137 | try:
138 | with open(path, 'r', encoding='utf-8') as file:
139 | return file.read()
140 | except FileNotFoundError:
141 | print(f"File not found: {path}")
142 | return ""
143 |
144 | def write_instruction_enhance_prompt(Using_character_name, Instruction_enhance_prompt):
145 | global AIVT_Character_path
146 |
147 | path = os.path.join(AIVT_Character_path, Using_character_name, "instruction_prompt.txt")
148 |
149 | try:
150 | with open(path, 'w', encoding='utf-8') as file:
151 | file.write(Instruction_enhance_prompt)
152 | #print(f"!!! *{Using_character_name}* Write Instruction Enhance Prompt Susses !!!")
153 | except:
154 | print(f"!!! *{Using_character_name}* Write Instruction Enhance Prompt Failed !!!")
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | OpenAI_Whisper_Inference = "Local"
166 | OpenAI_Whisper_LLM_wait_list = []
167 |
168 | lock_OpenAI_Whisper = threading.Lock()
169 |
170 | def OpenAI_Whisper_thread(audio_frames, command=None):
171 | global GUI_User_Name, OpenAI_Whisper_LLM_wait_list, OpenAI_Whisper_Inference
172 |
173 | with lock_OpenAI_Whisper:
174 | user_mic_audio_path = Mic_Record.User_Mic_parameters["user_mic_audio_path"]
175 |
176 | Mic_Record.save_audio2wav(audio_frames, user_mic_audio_path, )
177 |
178 | aprint("* Whisper Transcribing... *")
179 |
180 | if OpenAI_Whisper_Inference == "Local":
181 | ans_OpenAI_Whisper_text = whisper.run_with_timeout_OpenAI_Whisper(
182 | audio_path = user_mic_audio_path,
183 | audio_language = whisper.whisper_parameters["user_mic_language"],
184 | prompt = whisper.whisper_parameters["prompt"],
185 | max_tokens = whisper.whisper_parameters["max_tokens"],
186 | temperature = whisper.whisper_parameters["temperature"],
187 | timeout = whisper.whisper_parameters["timeout"]
188 | )
189 |
190 | else:
191 | ans_OpenAI_Whisper_text = whisper_api.run_with_timeout_OpenAI_Whisper_API(
192 | model = whisper_api.whisper_parameters["model"],
193 | audio_path = user_mic_audio_path,
194 | audio_language = whisper_api.whisper_parameters["user_mic_language"],
195 | prompt = whisper_api.whisper_parameters["prompt"],
196 | temperature = whisper_api.whisper_parameters["temperature"],
197 | timeout = whisper_api.whisper_parameters["timeout"]
198 | )
199 |
200 |
201 | if ans_OpenAI_Whisper_text != "":
202 | role = "user_mic"
203 | ans_OpenAI_Whisper_text = GUI_User_Name + " : " + ans_OpenAI_Whisper_text
204 | ans_requst = {"role": role, "content": ans_OpenAI_Whisper_text}
205 | OpenAI_Whisper_LLM_wait_list.append(ans_requst)
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | GUI_LLM_parameters = {
217 | "model": "",
218 | "instruction_enhance": False,
219 | "instruction_enhance_i": 3,
220 | "instruction_enhance_prompt": "",
221 | "wdn_prompt": "",
222 | }
223 |
224 | lock_LLM_Request = threading.Lock()
225 |
226 | def LLM_Request_thread(ans_request, llm_ans=None):
227 | global GUI_LLM_parameters, conversation
228 |
229 | with lock_LLM_Request:
230 | role = ans_request["role"]
231 | conversation_now = ans_request["content"]
232 |
233 | if role == "assistant":
234 | llm_ans.put("")
235 | return
236 |
237 |
238 | if GUI_LLM_parameters["instruction_enhance"]:
239 | conversation_instruction = GUI_LLM_parameters["instruction_enhance_prompt"]
240 | else:
241 | conversation_instruction = ""
242 |
243 |
244 | llm_model_name = ""
245 | token_max = 0
246 |
247 | if GUI_LLM_parameters["model"] == "Gemini":
248 | llm_model_name = gimini_api.gemini_parameters["model"]
249 | token_max = gimini_api.gemini_parameters["max_input_tokens"] - 10
250 | elif GUI_LLM_parameters["model"] == "GPT":
251 | llm_model_name = gpt_api.gpt_parameters["model"]
252 | token_max = gpt_api.gpt_parameters["max_input_tokens"]
253 | else:
254 | llm_model_name = gimini_api.gemini_parameters["model"]
255 | token_max = gimini_api.gemini_parameters["max_input_tokens"]
256 |
257 |
258 | conversation_for_llm = copy.deepcopy(conversation)
259 |
260 | iea = False
261 | iei = GUI_LLM_parameters["instruction_enhance_i"]*2
262 | if len(conversation) >= 6+iei:
263 | iea = True
264 | conversation_for_llm.insert(len(conversation)-iei, {'role': 'system', 'content': conversation_instruction})
265 |
266 | chat_role = ["user", "user_mic", "youtube_chat", "twitch_chat"]
267 |
268 | if role in chat_role:
269 | conversation.append({"role": "user", "content": conversation_now})
270 | conversation_for_llm.append({"role": "user", "content": conversation_now})
271 |
272 |
273 | token_now = tokenC.num_tokens_from_conversation(conversation_for_llm, llm_model_name)
274 | print(f"LLM Request input tokens {token_now}")
275 |
276 | cp = 7
277 | if iea:
278 | cp += 1
279 |
280 | while token_now > token_max:
281 | if len(conversation_for_llm) <= cp:
282 | print("!!! Plz Increse Max Input Tokens Or Reduce The Prompt !!!")
283 | llm_ans.put("")
284 | return
285 |
286 | conversation.pop(6)
287 | conversation_for_llm.pop(6)
288 | token_now = tokenC.num_tokens_from_conversation(conversation_for_llm, llm_model_name)
289 | print(f"LLM Request input tokens {token_now}")
290 |
291 |
292 | if GUI_LLM_parameters["model"] == "Gemini":
293 | llm_result = gimini_api.run_with_timeout_GoogleAI_Gemini_API(
294 | conversation_for_llm,
295 | conversation_now,
296 | model_name=gimini_api.gemini_parameters["model"],
297 | max_output_tokens=gimini_api.gemini_parameters["max_output_tokens"],
298 | temperature=gimini_api.gemini_parameters["temperature"],
299 | timeout=gimini_api.gemini_parameters["timeout"],
300 | retry=gimini_api.gemini_parameters["retry"],
301 | )
302 |
303 | elif GUI_LLM_parameters["model"] == "GPT":
304 | llm_result = gpt_api.run_with_timeout_OpenAI_GPT_API(
305 | conversation_for_llm,
306 | conversation_now,
307 | model_name=gpt_api.gpt_parameters["model"],
308 | max_output_tokens=gpt_api.gpt_parameters["max_output_tokens"],
309 | temperature=gpt_api.gpt_parameters["temperature"],
310 | timeout=gpt_api.gpt_parameters["timeout"],
311 | retry=gpt_api.gpt_parameters["retry"],
312 | )
313 |
314 | else:
315 | llm_ans.put("")
316 | return
317 |
318 |
319 | if llm_result == None or llm_result == "":
320 | if llm_result == None:
321 | print(f"LLM requset timeout!!!")
322 | else:
323 | print(f"LLM requset failed!!!")
324 |
325 | llm_ans.put("")
326 | return
327 |
328 |
329 | elif llm_result != "":
330 | conversation.append({"role": "assistant", "content": f"{llm_result}"})
331 | llm_ans.put(llm_result)
332 | return
333 |
334 | #print(f"\n{conversation}\n")
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 | GUI_TTS_Using = ""
346 | GUI_Setting_read_chat_now = False
347 | GUI_VTSP_wait_until_hotkeys_complete = False
348 |
349 | GUI_AIVT_Speaking_wait_list = []
350 | GUI_Conversation_History_list = []
351 |
352 |
353 | def subtitles_speak_checker():
354 | global GUI_Running, GUI_AIVT_Speaking_wait_list, GUI_Conversation_History_list
355 |
356 | while GUI_Running:
357 | if len(GUI_AIVT_Speaking_wait_list) > 0:
358 | sst_arg = GUI_AIVT_Speaking_wait_list.pop(0)
359 | GUI_Conversation_History_list.append(sst_arg)
360 |
361 | try:
362 | threading.Thread(
363 | target=subtitles_speak_thread,
364 | args=(
365 | sst_arg["chat_role"],
366 | sst_arg["chat_now"],
367 | sst_arg["ai_ans"],
368 | sst_arg["ai_name"],
369 | ),
370 | ).start()
371 |
372 | except Exception as e:
373 | print(f"\n{e}\n")
374 |
375 | time.sleep(0.1)
376 |
377 |
378 |
379 | lock_sa_requst = threading.Lock()
380 | lock_tts_request = threading.Lock()
381 | lock_subtitles_speak = threading.Lock()
382 | lock_remove_file = threading.Lock()
383 | speaking_continue_count = 0
384 | speaking_continue_vtsp_hkns = []
385 |
386 | def subtitles_speak_thread(chat_role, chat_now, ai_ans, ai_name):
387 | global GUI_Setting_read_chat_now, speaking_continue_count
388 |
389 | read_chat_role = ["user", "user_mic"]
390 | read_chat_now = (GUI_Setting_read_chat_now and chat_role in read_chat_role) or (live_chat.Live_chat_parameters["yt_live_chat_read_chat_now"] and chat_role == "youtube_chat") or (live_chat.Live_chat_parameters["tw_live_chat_read_chat_now"] and chat_role == "twitch_chat")
391 |
392 | AIVT_VTSP_Status_authenticated = vtsp.AIVT_VTSP_Status["authenticated"]
393 |
394 | sa_enble = vtsp.AIVT_hotkeys_parameters["sentiment_analysis"] and AIVT_VTSP_Status_authenticated
395 | if sa_enble:
396 | sa_ans = queue.Queue()
397 | sar = threading.Thread(target=sa_request_thread, args=(ai_ans, sa_ans, ))
398 | sar.start()
399 | else:
400 | emo_state = ""
401 |
402 |
403 | chat_now_tts_path = ""
404 | if read_chat_now:
405 | chat_now_tts_path = queue.Queue()
406 | tts_chat_now = chat_now.replace(";)", ")")
407 | tts_chat_now = tts_chat_now.replace(";)", ")")
408 | tr_cn = threading.Thread(target=tts_request_thread, args=(tts_chat_now, chat_now_tts_path, ))
409 | tr_cn.start()
410 |
411 | ai_ans_tts_path = queue.Queue()
412 | tr_aa = threading.Thread(target=tts_request_thread, args=(ai_ans.replace(";)", ")"), ai_ans_tts_path, ))
413 | tr_aa.start()
414 |
415 | if sa_enble:
416 | sar.join()
417 | emo_state = sa_ans.get()
418 |
419 |
420 | if read_chat_now:
421 | tr_cn.join()
422 | chat_now_tts_path = chat_now_tts_path.get()
423 |
424 |
425 | tr_aa.join()
426 | ai_ans_tts_path = ai_ans_tts_path.get()
427 |
428 | speaking_continue_count += 1
429 |
430 | with lock_subtitles_speak:
431 | subtitles_speak(
432 | chat_role,
433 | chat_now,
434 | ai_ans,
435 | ai_name,
436 | chat_now_tts_path,
437 | ai_ans_tts_path,
438 | emo_state,
439 | AIVT_VTSP_Status_authenticated=AIVT_VTSP_Status_authenticated
440 | )
441 |
442 | speaking_continue_count -= 1
443 |
444 |
445 | with lock_remove_file:
446 | def manage_files_by_count(file_path, file_max):
447 | try:
448 | file_max = int(file_max)
449 | except ValueError:
450 | file_max = 10
451 | if file_max < 0:
452 | file_max = 10
453 |
454 | files = [os.path.join(file_path, f) for f in os.listdir(file_path) if os.path.isfile(os.path.join(file_path, f))]
455 | if len(files) <= file_max:
456 | return
457 |
458 | files.sort()
459 | while len(files) > file_max:
460 | os.remove(files[0])
461 | files.pop(0)
462 |
463 | file_path = "Audio/tts"
464 | file_max = 10
465 | manage_files_by_count(file_path, file_max)
466 |
467 |
468 |
469 | def sa_request_thread(text, sa_ans):
470 | with lock_sa_requst:
471 | sa_ans.put(sa_nlp_api.Sentiment_Analysis_NLP(text, Emo_state_categories=vtsp.AIVT_hotkeys_parameters["Emo_state_categories"], model=vtsp.AIVT_hotkeys_parameters["sentiment_analysis_model"]))
472 |
473 |
474 | def tts_request_thread(tts_text, tts_path):
475 | global GUI_TTS_Using
476 |
477 | with lock_tts_request:
478 | def generate_unique_filename(file_path, file_name, file_extension):
479 | timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
480 | base_filename = f"{timestamp}_{file_name}"
481 | full_filename = f"{base_filename}{file_extension}"
482 | full_path = os.path.join(file_path, full_filename)
483 | counter = 1
484 |
485 | while os.path.exists(full_path):
486 | full_filename = f"{base_filename}_{counter}{file_extension}"
487 | full_path = os.path.join(file_path, full_filename)
488 | counter += 1
489 |
490 | return full_path
491 |
492 | file_path = "Audio/tts"
493 | file_name = f"{GUI_TTS_Using}"
494 | if GUI_TTS_Using == "EdgeTTS":
495 | file_extension = ".mp3"
496 | elif GUI_TTS_Using == "OpenAITTS":
497 | file_extension = ".wav"
498 | else:
499 | file_extension = ".mp3"
500 |
501 | unique_filename = generate_unique_filename(file_path, file_name, file_extension)
502 |
503 | if GUI_TTS_Using == "EdgeTTS":
504 | output_path = unique_filename
505 | voice = edgeTTS.edgetts_parameters["voice"]
506 | pitch = edgeTTS.edgetts_parameters["pitch"]
507 | rate = edgeTTS.edgetts_parameters["rate"]
508 | volume = edgeTTS.edgetts_parameters["volume"]
509 | timeout = edgeTTS.edgetts_parameters["timeout"]
510 |
511 | ttsr = threading.Thread(
512 | target=edgeTTS.edgetts,
513 | kwargs={
514 | "text":tts_text,
515 | "output_path":output_path,
516 | "voice":voice,
517 | "pitch":pitch,
518 | "rate":rate,
519 | "volume":volume,
520 | "timeout":timeout,
521 | }
522 | )
523 | ttsr.start()
524 |
525 | elif GUI_TTS_Using == "OpenAITTS":
526 | output_path = unique_filename
527 | model = openaiTTS.openaitts_parameters["model"]
528 | voice = openaiTTS.openaitts_parameters["voice"]
529 | speed = openaiTTS.openaitts_parameters["speed"]
530 | format = openaiTTS.openaitts_parameters["format"]
531 | timeout = openaiTTS.openaitts_parameters["timeout"]
532 |
533 | ttsr = threading.Thread(
534 | target=openaiTTS.openaitts,
535 | kwargs={
536 | "text":tts_text,
537 | "output_path":output_path,
538 | "model":model,
539 | "voice":voice,
540 | "speed":speed,
541 | "format":format,
542 | "timeout":timeout,
543 | }
544 | )
545 | ttsr.start()
546 |
547 | else:
548 | output_path = unique_filename
549 | voice = edgeTTS.edgetts_parameters["voice"]
550 | pitch = edgeTTS.edgetts_parameters["pitch"]
551 | rate = edgeTTS.edgetts_parameters["rate"]
552 | volume = edgeTTS.edgetts_parameters["volume"]
553 | timeout = edgeTTS.edgetts_parameters["timeout"]
554 |
555 | ttsr = threading.Thread(
556 | target=edgeTTS.edgetts,
557 | kwargs={
558 | "text":tts_text,
559 | "output_path":output_path,
560 | "voice":voice,
561 | "pitch":pitch,
562 | "rate":rate,
563 | "volume":volume,
564 | "timeout":timeout,
565 | }
566 | )
567 | ttsr.start()
568 |
569 | ttsr.join()
570 |
571 | tts_path.put(unique_filename)
572 | return
573 |
574 |
575 | def Subtitles_formatter_v3(text, max_length, english_char_length, base_line_count):
576 | def format_text(text, max_length, english_char_length, base_line_count):
577 | lines = [line for line in text.split('\n') if line.strip() != '']
578 | Predicted_lines = []
579 | for line in lines:
580 | while len(line) > max_length:
581 | Predicted_lines.append(line[:max_length])
582 | line = line[max_length:]
583 | Predicted_lines.append(line)
584 |
585 | Predicted_line_count = len(Predicted_lines)
586 | #print(f'\nPredicted line count: {Predicted_line_count}\n')
587 |
588 | if Predicted_line_count < base_line_count:
589 | line_count = base_line_count
590 | else:
591 | line_count = Predicted_line_count
592 | new_max_length = max_length * line_count / base_line_count
593 | #print(f'Iteration BASE: line_count={line_count}, new_max_length={new_max_length}')
594 | result = split_line_text(lines, math.ceil(new_max_length), english_char_length, line_count)
595 |
596 | iteration = 1
597 | prev_line_count = None
598 | #print(str(len(result) != line_count) + " -> len(result) != line_count")
599 | #print(str(prev_line_count is None or prev_line_count != line_count) + " -> (prev_line_count is None or prev_line_count != line_count)")
600 | #print(str(iteration < 10) + " -> iteration < 10")
601 | #print(str(lines_fit_check != -1) + " -> lines_fit_check != -1")
602 | #print("\n----------\n")
603 | while len(result) != line_count and (prev_line_count is None or prev_line_count != line_count) and iteration < 10:
604 | #print(f'Iteration {iteration}: line_count={line_count}, new_max_length={new_max_length}')
605 | prev_line_count = line_count
606 | lr = len(result)
607 | #print(f'len(result) {lr}\n')
608 | line_count = (len(result) + line_count) // 2
609 | new_max_length = max_length * line_count / base_line_count
610 | #print(f'Iteration {iteration}: line_count={line_count}, new_max_length={new_max_length}')
611 | result = split_line_text(lines, math.ceil(new_max_length), english_char_length, line_count)
612 | iteration += 1
613 |
614 | fix_iteration_line_count = (len(result) + line_count) // 2
615 | #print(f'fix_iteration_line_count {fix_iteration_line_count}')
616 | if fix_iteration_line_count < base_line_count:
617 | line_count = base_line_count
618 | else:
619 | line_count = len(result) + 1
620 |
621 | lr = len(result)
622 | #print(f'len(result) {lr}')
623 | #print(f'line_count {line_count}')
624 | #if len(result) == line_count:
625 | #line_count = lr + 1
626 | #line_count = lr + 1
627 |
628 | new_max_length = max_length * line_count / base_line_count
629 | #print(f'fix iteration parameters: line_count={line_count}, new_max_length={new_max_length}')
630 | result = split_line_text(lines, math.ceil(new_max_length), english_char_length, line_count)
631 |
632 | final_iteration_line_count = len(result)
633 | #print(f'\nFinal result: line_count={final_iteration_line_count}, max_length={new_max_length}')
634 | #print(f'{result}')
635 | #print("\n----------\n")
636 | return '\n'.join(result)
637 |
638 | def split_line_text(lines: str, max_length: int, english_char_length: float, line_count: int):
639 | global lines_fit_check
640 | result = []
641 | lines_fit_check = 0
642 | for line in lines:
643 |
644 | while len(line) > 0:
645 | #print(line)
646 | actual_length = 0
647 | split_index = 0
648 |
649 | for i, char in enumerate(line):
650 | if re.match(r' [a-zA-Z]\(\)\[\]{}<>:;=,。!?;~,!?~;【】「」『』〔〕()┐┌ლ✧⁄/╧ノメ彡o。丿•̥̥̥`´゚ゞو', char):
651 | actual_length += english_char_length
652 | else:
653 | actual_length += 1
654 | if actual_length > max_length:
655 | #print("----------")
656 | #print(f'max_length {max_length}')
657 | #print(f'actual_length {actual_length}')
658 | break
659 | split_index = i + 1
660 |
661 | if actual_length > max_length:
662 | punctuation_index = [i for i in range(split_index) if line[i] in ' ,。!?;~,!?~;【】「」『』〔〕()┐┌ლ✧⁄\/╧ノメ彡o。つ丿ㄟㄏ︴Ψ']
663 | if punctuation_index:
664 | if line[punctuation_index[-1]] in '【「『〔((┐┌ㄟ\': #::】」』〕)┌ლ✧⁄╧╧ノメ彡o。丿
665 | split_index = punctuation_index[-1]
666 | else:
667 | split_index = punctuation_index[-1] + 1
668 |
669 | #print(f'split_index {split_index}')
670 | result.append(line[:split_index])
671 | line = line[split_index:].lstrip()
672 | else:
673 | result.append(line)
674 | line = ""
675 | lines_fit_check += 1
676 | #print(f'lines_fit_check {lines_fit_check}')
677 |
678 | if lines_fit_check == line_count:
679 | lines_fit_check = -1
680 | #print(f'lines_fit_check {lines_fit_check}')
681 | #print("\n----------\n")
682 | return result
683 |
684 | return format_text(text, max_length, english_char_length, base_line_count)
685 |
686 | def Subtitles_formatter_v2(text, max_length, english_char_length, base_line_count):
687 | def find_natural_break(line, max_length):
688 | total_length = 0
689 | last_good_break = 0
690 | for i, char in enumerate(line):
691 | char_length = english_char_length if char.isalpha() else 1
692 | total_length += char_length
693 |
694 | if char in ' ,.!?;:':
695 | last_good_break = i + 1
696 |
697 | if total_length > max_length:
698 | if last_good_break > 0:
699 | return last_good_break
700 | break
701 |
702 | return last_good_break if last_good_break > 0 else i
703 |
704 | def process_line_splits(lines, max_length):
705 | new_lines = []
706 | for line in lines:
707 | while len(line) > max_length:
708 | break_index = find_natural_break(line, max_length)
709 | if break_index == 0:
710 | break_index = len(line)
711 | new_lines.append(line[:break_index].strip())
712 | line = line[break_index:].lstrip()
713 | new_lines.append(line.strip())
714 | return new_lines
715 |
716 | lines = text.split('\n')
717 | processed_lines = []
718 | for line in lines:
719 | if line.strip() == '':
720 | continue
721 | processed_lines.append(line.strip())
722 |
723 | iteration = 0
724 | while iteration < 10:
725 | new_lines = process_line_splits(processed_lines, max_length)
726 | if len(new_lines) <= base_line_count or len(new_lines) == len(processed_lines):
727 | break
728 | max_length = int(max_length * (len(new_lines) / base_line_count))
729 | processed_lines = new_lines
730 | iteration += 1
731 |
732 | return '\n'.join(new_lines)
733 |
734 |
735 |
736 | def subtitles_speak_sleep(stime):
737 | time.sleep(stime)
738 |
739 | def subtitles_speak(
740 | chat_role,
741 | chat_now,
742 | ai_ans,
743 | ai_name,
744 | chat_now_tts_path,
745 | ai_ans_tts_path,
746 | emo_state,
747 | AIVT_VTSP_Status_authenticated=False,
748 | command=None
749 | ):
750 | global GUI_Setting_read_chat_now, GUI_VTSP_wait_until_hotkeys_complete
751 | global speaking_continue_count, speaking_continue_vtsp_hkns
752 |
753 |
754 | show_chat_now = (chat_role in ["user", "user_mic", "youtube_chat", "twitch_chat"])
755 | read_chat_now = show_chat_now and ((GUI_Setting_read_chat_now and chat_role in ["user", "user_mic"]) or (live_chat.Live_chat_parameters["yt_live_chat_read_chat_now"] and chat_role == "youtube_chat")) or (live_chat.Live_chat_parameters["tw_live_chat_read_chat_now"] and chat_role == "twitch_chat")
756 |
757 |
758 | obs_show_chat_now = show_chat_now and obsws.OBS_Connected and obsws.OBS_chat_now_sub_parameters["show"]
759 | obs_show_ai_ans = obsws.OBS_Connected and obsws.OBS_ai_ans_sub_parameters["show"]
760 |
761 | obs_sub_formatter = obsws.OBS_subtitles_parameters["subtitles_formatter"]
762 | obs_sub_formatter_ver = obsws.OBS_subtitles_parameters["subtitles_formatter_version"]
763 |
764 | obs_chat_now_sub_name = obsws.OBS_chat_now_sub_parameters["sub_name"]
765 | obs_chat_now_show_sub_filter_names = obsws.OBS_chat_now_sub_parameters["show_sub_filter_names"]
766 | obs_chat_now_hide_sub_filter_names = obsws.OBS_chat_now_sub_parameters["hide_sub_filter_names"]
767 |
768 | obs_ai_ans_sub_name = obsws.OBS_ai_ans_sub_parameters["sub_name"]
769 | obs_ai_ans_show_sub_filter_names = obsws.OBS_ai_ans_sub_parameters["show_sub_filter_names"]
770 | obs_ai_ans_hide_sub_filter_names = obsws.OBS_ai_ans_sub_parameters["hide_sub_filter_names"]
771 |
772 | vtsp_hk_enble = AIVT_VTSP_Status_authenticated
773 | vtsp_trigger_first = vtsp.AIVT_hotkeys_parameters["trigger_first"]
774 |
775 |
776 |
777 | if vtsp_hk_enble and emo_state != "":
778 | emo_kn = vtsp.AIVT_hotkeys_parameters[f"{emo_state}_kn"]
779 | vtsp_hotkey_names = vtsp.get_hotkey_names(emo_kn)
780 |
781 | elif vtsp_hk_enble:
782 | emo = random.choice(vtsp.AIVT_hotkeys_parameters["Emo_state_categories"])
783 | print(f"VTSP Random Emotion State: {emo}")
784 | emo_kn = vtsp.AIVT_hotkeys_parameters[f"{emo}_kn"]
785 | vtsp_hotkey_names = vtsp.get_hotkey_names(emo_kn)
786 |
787 |
788 | if vtsp_hk_enble and vtsp_trigger_first:
789 | if speaking_continue_vtsp_hkns:
790 | o_vtsp_hkns = set(vtsp_hotkey_names["all"])
791 | p_vtsp_hkns = set(speaking_continue_vtsp_hkns)
792 | d_vtsp_hkns = o_vtsp_hkns & p_vtsp_hkns
793 |
794 | no_vtsp_hkns = [item for item in vtsp_hotkey_names["all"] if item not in d_vtsp_hkns]
795 | np_vtsp_hkns = [item for item in speaking_continue_vtsp_hkns if item not in d_vtsp_hkns]
796 |
797 | n_vtsp_hkns = np_vtsp_hkns + no_vtsp_hkns
798 |
799 | else:
800 | n_vtsp_hkns = vtsp_hotkey_names["all"]
801 |
802 | vtsp_ht = threading.Thread(
803 | target=vtsp.VTSP_Hotkey_Names_Trigger,
804 | kwargs={"hotkey_names_list": n_vtsp_hkns},
805 | )
806 | vtsp_ht.start()
807 | if GUI_VTSP_wait_until_hotkeys_complete:
808 | vtsp_ht.join()
809 |
810 | speaking_continue_vtsp_hkns = []
811 |
812 |
813 |
814 | if obs_show_chat_now:
815 | if obs_sub_formatter:
816 | lines = chat_now.splitlines()
817 | lines = [line for line in lines if line.strip() != '']
818 | obs_chat_now = '\n'.join(lines)
819 |
820 | if obs_sub_formatter_ver == "v3":
821 | obs_chat_now = Subtitles_formatter_v3(
822 | obs_chat_now,
823 | obsws.OBS_chat_now_sub_parameters["sub_max_length"],
824 | obsws.OBS_chat_now_sub_parameters["sub_english_char_length"],
825 | obsws.OBS_chat_now_sub_parameters["sub_base_line_count"]
826 | )
827 |
828 | elif obs_sub_formatter_ver == "v2":
829 | obs_chat_now = Subtitles_formatter_v2(
830 | obs_chat_now,
831 | obsws.OBS_chat_now_sub_parameters["sub_max_length"],
832 | obsws.OBS_chat_now_sub_parameters["sub_english_char_length"],
833 | obsws.OBS_chat_now_sub_parameters["sub_base_line_count"]
834 | )
835 |
836 | else:
837 | obs_chat_now = Subtitles_formatter_v3(
838 | obs_chat_now,
839 | obsws.OBS_chat_now_sub_parameters["sub_max_length"],
840 | obsws.OBS_chat_now_sub_parameters["sub_english_char_length"],
841 | obsws.OBS_chat_now_sub_parameters["sub_base_line_count"]
842 | )
843 |
844 | obst_cn_st = threading.Thread(
845 | target=obsws.Set_Source_Text,
846 | args=(obs_chat_now_sub_name, obs_chat_now)
847 | )
848 | obst_cn_st.start()
849 | obst_cn_st.join()
850 |
851 | if len(obs_chat_now_show_sub_filter_names) > 0:
852 | obst_cn_s = threading.Thread(
853 | target=obsws.Set_Source_Filter_Enabled,
854 | args=(
855 | obs_chat_now_sub_name,
856 | obs_chat_now_show_sub_filter_names,
857 | True,
858 | ),
859 | kwargs={
860 | "start_delay":obsws.OBS_chat_now_sub_parameters["show_sub_filter_start_delay"],
861 | "end_delay":obsws.OBS_chat_now_sub_parameters["show_sub_filter_end_delay"],
862 | },
863 | )
864 | obst_cn_s.start()
865 |
866 | sub_time = obsws.OBS_chat_now_sub_parameters["sub_time"]
867 | if sub_time > 0:
868 | sss = threading.Thread(target=subtitles_speak_sleep, args=(sub_time, ))
869 | sss.start()
870 |
871 |
872 |
873 | if read_chat_now:
874 | chat_now_read = threading.Thread(target=speaking, args=(chat_now_tts_path, ))
875 | chat_now_read.start()
876 | chat_now_read.join()
877 |
878 |
879 |
880 | if obs_show_chat_now:
881 | if sub_time > 0:
882 | sss.join()
883 |
884 | if obs_show_chat_now:
885 | time.sleep(obsws.OBS_chat_now_sub_parameters["sub_end_delay"])
886 |
887 | if vtsp_hk_enble and not vtsp_trigger_first:
888 | vtsp_ht = threading.Thread(
889 | target=vtsp.VTSP_Hotkey_Names_Trigger,
890 | kwargs={"hotkey_names_list": vtsp_hotkey_names["all"]}
891 | )
892 | vtsp_ht.start()
893 | if GUI_VTSP_wait_until_hotkeys_complete:
894 | vtsp_ht.join()
895 |
896 |
897 |
898 | if obs_show_chat_now:
899 | if obsws.OBS_chat_now_sub_parameters["clear"]:
900 | obst_cn_st = threading.Thread(
901 | target=obsws.Set_Source_Text,
902 | args=(obs_chat_now_sub_name, "")
903 | )
904 | obst_cn_st.start()
905 | obst_cn_st.join()
906 |
907 | if len(obs_chat_now_hide_sub_filter_names) > 0:
908 | obst_cn_fe_sd = threading.Thread(
909 | target=obsws.Set_Source_Filter_Enabled,
910 | args=(
911 | obs_chat_now_sub_name,
912 | obs_chat_now_hide_sub_filter_names,
913 | True,
914 | ),
915 | kwargs={
916 | "start_delay":obsws.OBS_chat_now_sub_parameters["hide_sub_filter_start_delay"],
917 | "end_delay":obsws.OBS_chat_now_sub_parameters["hide_sub_filter_end_delay"],
918 | },
919 | )
920 | obst_cn_fe_sd.start()
921 |
922 |
923 |
924 | if obs_show_ai_ans:
925 |
926 | if obsws.OBS_ai_ans_sub_parameters["remove_original_text_wrap"]:
927 | obs_ai_ans = ai_ans.replace('\n', ' ')
928 | else:
929 | obs_ai_ans = ai_ans
930 |
931 | if obs_sub_formatter_ver == "v3":
932 | ai_ans_formatted = Subtitles_formatter_v3(
933 | obs_ai_ans,
934 | obsws.OBS_ai_ans_sub_parameters["sub_max_length"],
935 | obsws.OBS_ai_ans_sub_parameters["sub_english_char_length"],
936 | obsws.OBS_ai_ans_sub_parameters["sub_base_line_count"]
937 | )
938 |
939 | elif obs_sub_formatter_ver == "v2":
940 | ai_ans_formatted = Subtitles_formatter_v2(
941 | obs_ai_ans,
942 | obsws.OBS_ai_ans_sub_parameters["sub_max_length"],
943 | obsws.OBS_ai_ans_sub_parameters["sub_english_char_length"],
944 | obsws.OBS_ai_ans_sub_parameters["sub_base_line_count"]
945 | )
946 |
947 | else:
948 | ai_ans_formatted = Subtitles_formatter_v3(
949 | obs_ai_ans,
950 | obsws.OBS_ai_ans_sub_parameters["sub_max_length"],
951 | obsws.OBS_ai_ans_sub_parameters["sub_english_char_length"],
952 | obsws.OBS_ai_ans_sub_parameters["sub_base_line_count"]
953 | )
954 |
955 | obst_aa_st = threading.Thread(
956 | target=obsws.Set_Source_Text,
957 | args=(obs_ai_ans_sub_name, ai_ans_formatted)
958 | )
959 | obst_aa_st.start()
960 | obst_aa_st.join()
961 |
962 |
963 |
964 | ai_ans_read = threading.Thread(target=speaking, args=(ai_ans_tts_path, ))
965 |
966 |
967 |
968 | if obs_show_ai_ans:
969 | if len(obs_ai_ans_show_sub_filter_names) > 0:
970 | obst_aa_fe_s = threading.Thread(
971 | target=obsws.Set_Source_Filter_Enabled,
972 | args=(
973 | obs_ai_ans_sub_name,
974 | obs_ai_ans_show_sub_filter_names,
975 | True,
976 | ),
977 | kwargs={
978 | "start_delay":obsws.OBS_ai_ans_sub_parameters["show_sub_filter_start_delay"],
979 | "end_delay":obsws.OBS_ai_ans_sub_parameters["show_sub_filter_end_delay"],
980 | },
981 | )
982 | obst_aa_fe_s.start()
983 |
984 |
985 |
986 | ai_ans_read.start()
987 | ai_ans_read.join()
988 |
989 |
990 |
991 | if obs_show_ai_ans:
992 | time.sleep(obsws.OBS_ai_ans_sub_parameters["sub_end_delay"])
993 |
994 |
995 |
996 | if vtsp_hk_enble and (speaking_continue_count <= 1 or not vtsp_trigger_first):
997 | idle_kn = vtsp.get_hotkey_names(vtsp.AIVT_hotkeys_parameters["idle_ani"], command="idle_ani")
998 | vtsp_hkns = idle_kn["r_ani"] + vtsp_hotkey_names["all_exp"]
999 |
1000 | vtsp_ht = threading.Thread(
1001 | target=vtsp.VTSP_Hotkey_Names_Trigger,
1002 | kwargs={"hotkey_names_list": vtsp_hkns}
1003 | )
1004 | vtsp_ht.start()
1005 |
1006 | elif vtsp_hk_enble:
1007 | speaking_continue_vtsp_hkns = vtsp_hotkey_names["all_exp"]
1008 |
1009 |
1010 | if vtsp_hk_enble and GUI_VTSP_wait_until_hotkeys_complete:
1011 | vtsp_ht.join()
1012 |
1013 |
1014 | if obs_show_ai_ans:
1015 | if obsws.OBS_ai_ans_sub_parameters["clear"]:
1016 | obst_aa_st = threading.Thread(
1017 | target=obsws.Set_Source_Text,
1018 | args=(obs_ai_ans_sub_name, "")
1019 | )
1020 | obst_aa_st.start()
1021 | obst_aa_st.join()
1022 |
1023 | if len(obs_ai_ans_hide_sub_filter_names) > 0:
1024 | obst_aa_fe_h = threading.Thread(
1025 | target=obsws.Set_Source_Filter_Enabled,
1026 | args=(
1027 | obs_ai_ans_sub_name,
1028 | obs_ai_ans_hide_sub_filter_names,
1029 | True,
1030 | ),
1031 | kwargs={
1032 | "start_delay":obsws.OBS_ai_ans_sub_parameters["hide_sub_filter_start_delay"],
1033 | "end_delay":obsws.OBS_ai_ans_sub_parameters["hide_sub_filter_end_delay"],
1034 | },
1035 | )
1036 | obst_aa_fe_h.start()
1037 |
1038 |
1039 |
1040 | try:
1041 | w_name, w_text = chat_now.split(" : ", 1)
1042 | w_chat_now = f"{w_name} :\n{w_text}"
1043 | except:
1044 | w_chat_now = chat_now
1045 | w_ai_ans = f"{ai_name} :\n{ai_ans}"
1046 | wch = threading.Thread(target=write_conversation_history, args=(w_chat_now, w_ai_ans))
1047 | wch.start()
1048 |
1049 |
1050 |
1051 | def speaking(audio_path, start_delay=0, end_delay=0):
1052 | ai_voice_output_device_name = Play_Audio.play_audio_parameters["ai_voice_output_device_name"]
1053 |
1054 | time.sleep(start_delay)
1055 |
1056 | try:
1057 | Play_Audio.PlayAudio(audio_path, ai_voice_output_device_name)
1058 |
1059 | except Exception as e:
1060 | print(f"!!! Play Audio Error !!!\n{e}")
1061 |
1062 | time.sleep(end_delay)
1063 |
1064 |
1065 |
1066 | lock_write_conversation_history = threading.Lock()
1067 |
1068 | def write_conversation_history(chat_now, subtitles):
1069 | with lock_write_conversation_history:
1070 | ConversationHistory_path = f"Text_files/ConversationHistory/{datetime.datetime.now().strftime('%Y-%m-%d')}.txt"
1071 |
1072 | with open(ConversationHistory_path, "a", encoding="utf-8") as outfile:
1073 | outfile.write(f"\n{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S %p')}\n")
1074 | outfile.write(f"\n{chat_now}\n")
1075 | outfile.write(f"\n{subtitles}\n")
1076 | outfile.write("\n------------------------------\n")
1077 |
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 | def AIVT_VTSP_Idle_Animation():
1088 | global speaking_continue_count
1089 | if vtsp.AIVT_VTSP_Status["authenticated"]:
1090 | kn = vtsp.get_hotkey_names(vtsp.AIVT_hotkeys_parameters["idle_ani"], command="idle_ani")
1091 | vtsp_ht = threading.Thread(
1092 | target=vtsp.VTSP_Hotkey_Names_Trigger,
1093 | kwargs={"hotkey_names_list": kn["r_ani"]}
1094 | )
1095 | vtsp_ht.start()
1096 |
1097 | t = 0
1098 |
1099 | while vtsp.AIVT_VTSP_Status["authenticated"]:
1100 | while speaking_continue_count > 0:
1101 | t = 0
1102 | time.sleep(0.1)
1103 |
1104 | if t >= 59:
1105 | t = 0
1106 |
1107 | kn = vtsp.get_hotkey_names(vtsp.AIVT_hotkeys_parameters["idle_ani"], command="idle_ani")
1108 | vtsp_ht = threading.Thread(
1109 | target=vtsp.VTSP_Hotkey_Names_Trigger,
1110 | kwargs={"hotkey_names_list": kn["r_ani"]}
1111 | )
1112 | vtsp_ht.start()
1113 |
1114 | time.sleep(1)
1115 | t += 1
1116 |
1117 |
1118 |
1119 |
1120 |
--------------------------------------------------------------------------------
/GUI_control_panel/GUI_config.ini:
--------------------------------------------------------------------------------
1 | [Main]
2 | show_chat_role = True
3 |
4 | [Setting]
5 | user_name = PKevin
6 | character = Midokyoko_Kyurei
7 | tab_position = North
8 | ai_voice_output_device_name = Microsoft 音效對應表 - Output
9 | user_mic_input_device_name = Microsoft 音效對應表 - Input
10 | doing_now = 今天是XXXX年XX月XX日。
11 | read_user_chat = False
12 | user_mic_hotkey_1_using = True
13 | user_mic_hotkey_1 = `
14 | user_mic_hotkey_2_using = True
15 | user_mic_hotkey_2 = caps lock
16 |
17 | [Live_Chat]
18 | yt_channel_name =
19 | yt_live_id = qTmJ4YkziEk
20 | yt_response_chatroom = False
21 | yt_live_chat_read_chat_now = False
22 | yt_response_owner = False
23 | yt_response_vip = False
24 | yt_response_individual = False
25 | yt_wait_list_max = 10
26 | yt_chat_max_tokens = 128
27 | yt_chat_max_response = 5
28 | yt_live_chat_vip_names =
29 | yt_live_chat_ban_names =
30 | tw_channel_name =
31 | tw_response_chatroom = False
32 | tw_live_chat_read_chat_now = False
33 | tw_response_owner = False
34 | tw_response_vip = False
35 | tw_response_individual = False
36 | tw_wait_list_max = 10
37 | tw_chat_max_tokens = 128
38 | tw_chat_max_response = 5
39 | tw_live_chat_vip_names =
40 | tw_live_chat_ban_names =
41 |
42 | [LLM]
43 | using = Gemini
44 | instruction_enhance = True
45 | instruction_enhance_i = 1
46 |
47 | [LLM_Gemini]
48 | model = gemini-2.0-flash
49 | max_input_tokens = 8192
50 | max_output_tokens = 512
51 | temperature = 0.9
52 | timeout = 15
53 | retry = 3
54 |
55 | [LLM_GPT]
56 | model = gpt-4o
57 | max_input_tokens = 4096
58 | max_output_tokens = 256
59 | temperature = 1.0
60 | timeout = 15
61 | retry = 3
62 |
63 | [TextToSpeech]
64 | using = EdgeTTS
65 |
66 | [EdgeTTS]
67 | voice = zh-TW-HsiaoChenNeural
68 | voice_male = zh-TW-YunJheNeural
69 | voice_female = zh-TW-HsiaoChenNeural
70 | pitch = 20
71 | rate = 14
72 | volume = 0
73 | timeout = 10
74 |
75 | [OpenAITTS]
76 | model = tts-1
77 | voice = alloy
78 | speed = 1.0
79 | timeout = 10
80 |
81 | [Whisper]
82 | inference = Local
83 | model_name = base
84 | model_api = whisper-1
85 | language = zh
86 | max_tokens = 192
87 | temperature = 0.2
88 | timeout = 10
89 | prompt =
90 |
91 | [OBS_Subtitles]
92 | subtitles_formatter = True
93 | subtitles_formatter_version = v3
94 |
95 | [OBS_Chat_now_sub]
96 | sub_name = AIVT_Chat_Sub
97 | show = True
98 | clear = False
99 | sub_time = 2.5
100 | sub_max_length = 30
101 | sub_english_char_length = 1.0
102 | sub_base_line_count = 7
103 | sub_end_delay = 0.0
104 | show_sub_filter_names = show
105 | hide_sub_filter_names = hide
106 | show_sub_filter_start_delay = 0.0
107 | hide_sub_filter_start_delay = 0.0
108 |
109 | [OBS_AI_ans_sub]
110 | sub_name = AIVT_AI_Sub
111 | show = True
112 | clear = False
113 | remove_original_text_wrap = True
114 | sub_max_length = 30
115 | sub_english_char_length = 1.0
116 | sub_base_line_count = 7
117 | sub_end_delay = 0.0
118 | show_sub_filter_names = show
119 | hide_sub_filter_names = hide
120 | show_sub_filter_start_delay = 0.0
121 | hide_sub_filter_start_delay = 0.0
122 |
123 | [VTube_Studio_Plug]
124 | trigger_first = True
125 | wait_until_hotkeys_complete = True
126 | sentiment_analysis = True
127 | sentiment_analysis_modle = gemini-2.0-flash
128 | idle_ani = !ani_idle_01/!ani_idle_02/!ani_idle_03
129 | normal = True
130 | normal_kn = !ani_talking_01/exp_smile1/exp_smile2
131 | happy = True
132 | happy_kn = !ani_talking_01/*exp_blush1/exp_smile2/exp_star_eyes/exp_heart_eyes
133 | shy = True
134 | shy_kn = !ani_talking_03/*exp_blush2/*exp_heart_eyes/exp_smile2/exp_smile3/exp_sad1
135 | proud = True
136 | proud_kn = !ani_talking_02/*exp_blush1/*exp_star_eyes/exp_smile2/exp_smile3
137 | shock = True
138 | shock_kn = !ani_talking_03/*exp_black_face/exp_sad1/exp_sad2
139 | sad = True
140 | sad_kn = !ani_talking_03/*exp_tear/*exp_sad1/exp_sad2/exp_black_face
141 | angry = True
142 | angry_kn = !ani_talking_02/*exp_angry/*exp_sad1/exp_sad2/exp_black_face
143 | embarrass = True
144 | embarrass_kn = !ani_talking_03/*exp_blush1/@exp_smile1/@exp_sad1/exp_blush2/exp_black_face
145 | afraid = True
146 | afraid_kn = !ani_talking_02/*exp_black_face/*exp_sad1/exp_sad2/exp_tear
147 | confuse = True
148 | confuse_kn = !ani_talking_03/*exp_smile1/exp_question_mark/exp_otaku_glasses
149 |
150 |
--------------------------------------------------------------------------------
/GUI_control_panel/GUI_icon/AI_Vtuber_GUI_control_panel_01.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/GUI_control_panel/GUI_icon/AI_Vtuber_GUI_control_panel_01.ico
--------------------------------------------------------------------------------
/GUI_control_panel/GUI_py/palette.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 255
8 | 255
9 | 255
10 |
11 |
12 |
13 |
14 |
15 |
16 | 40
17 | 40
18 | 40
19 |
20 |
21 |
22 |
23 |
24 |
25 | 255
26 | 255
27 | 255
28 |
29 |
30 |
31 |
32 |
33 |
34 | 255
35 | 255
36 | 255
37 |
38 |
39 |
40 |
41 |
42 |
43 | 40
44 | 40
45 | 40
46 |
47 |
48 |
49 |
50 |
51 |
52 | 40
53 | 40
54 | 40
55 |
56 |
57 |
58 |
59 |
60 |
61 | 255
62 | 255
63 | 255
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 255
73 | 255
74 | 255
75 |
76 |
77 |
78 |
79 |
80 |
81 | 40
82 | 40
83 | 40
84 |
85 |
86 |
87 |
88 |
89 |
90 | 255
91 | 255
92 | 255
93 |
94 |
95 |
96 |
97 |
98 |
99 | 255
100 | 255
101 | 255
102 |
103 |
104 |
105 |
106 |
107 |
108 | 40
109 | 40
110 | 40
111 |
112 |
113 |
114 |
115 |
116 |
117 | 40
118 | 40
119 | 40
120 |
121 |
122 |
123 |
124 |
125 |
126 | 255
127 | 255
128 | 255
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | 255
138 | 255
139 | 255
140 |
141 |
142 |
143 |
144 |
145 |
146 | 40
147 | 40
148 | 40
149 |
150 |
151 |
152 |
153 |
154 |
155 | 255
156 | 255
157 | 255
158 |
159 |
160 |
161 |
162 |
163 |
164 | 255
165 | 255
166 | 255
167 |
168 |
169 |
170 |
171 |
172 |
173 | 40
174 | 40
175 | 40
176 |
177 |
178 |
179 |
180 |
181 |
182 | 40
183 | 40
184 | 40
185 |
186 |
187 |
188 |
189 |
190 |
191 | 255
192 | 255
193 | 255
194 |
195 |
196 |
197 |
198 |
199 |
--------------------------------------------------------------------------------
/GUI_control_panel/GUI_py/ui2py_pysd6.txt:
--------------------------------------------------------------------------------
1 | !!! Run The Following Code In CMD Under The Corresponding File Path !!!
2 |
3 | pyside6-uic -o AI_Vtuber_control_panel_ui_pysd6.py AI_Vtuber_control_panel_ui_pysd6.ui
--------------------------------------------------------------------------------
/GUI_control_panel/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/GUI_control_panel/__init__.py
--------------------------------------------------------------------------------
/GUI_control_panel/ffmpeg.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/GUI_control_panel/ffmpeg.exe
--------------------------------------------------------------------------------
/Google/gemini/GoogleAI_Gemini_API.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4 | import threading
5 | import time
6 | import queue
7 |
8 | import google.generativeai as genai
9 |
10 | import AIVT_Config
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | gemini_models_max_input_tokens = {
22 | "gemini-2.0-flash":1048576,
23 | "gemini-2.0-flash-001":1048576,
24 | "gemini-2.0-flash-lite":1048576,
25 | "gemini-2.0-flash-lite-001":1048576,
26 | "gemini-2.5-pro-preview-03-25":1048576,
27 | "gemini-1.5-flash-latest":1048576,
28 | "gemini-1.5-flash":1048576,
29 | "gemini-1.5-flash-001":1048576,
30 | "gemini-1.5-flash-002":1048576,
31 | "gemini-1.5-flash-8b-latest":1048576,
32 | "gemini-1.5-flash-8b":1048576,
33 | "gemini-1.5-flash-8b-001":1048576,
34 | "gemini-1.5-pro-latest":2097152,
35 | "gemini-1.5-pro":2097152,
36 | "gemini-1.5-pro-001":2097152,
37 | "gemini-1.5-pro-002":2097152,
38 | }
39 |
40 | gemini_models_max_output_tokens = {
41 | "gemini-2.0-flash":8192,
42 | "gemini-2.0-flash-001":8192,
43 | "gemini-2.0-flash-lite":8192,
44 | "gemini-2.0-flash-lite-001":8192,
45 | "gemini-2.5-pro-preview-03-25":65536,
46 | "gemini-1.5-flash-latest":8192,
47 | "gemini-1.5-flash":8192,
48 | "gemini-1.5-flash-001":8192,
49 | "gemini-1.5-flash-002":8192,
50 | "gemini-1.5-flash-8b-latest":8192,
51 | "gemini-1.5-flash-8b":8192,
52 | "gemini-1.5-flash-8b-001":8192,
53 | "gemini-1.5-pro-latest":8192,
54 | "gemini-1.5-pro":8192,
55 | "gemini-1.5-pro-001":8192,
56 | "gemini-1.5-pro-002":8192,
57 | }
58 |
59 | gemini_parameters = {
60 | "model": "gemini-2.0-flash",
61 | "max_input_tokens" : 4096,
62 | "max_output_tokens" : 512,
63 | "temperature" : 0.90,
64 | "timeout" : 15,
65 | "retry" : 3,
66 | }
67 |
68 |
69 |
70 |
71 |
72 | def run_with_timeout_GoogleAI_Gemini_API(
73 | conversation,
74 | chatQ,
75 | model_name="gemini-2.0-flash",
76 | max_output_tokens="512",
77 | temperature=0.9,
78 | timeout=15,
79 | retry=3,
80 | command=None,
81 | ):
82 |
83 | start_time = time.time()
84 |
85 | ans = queue.Queue()
86 | prompt_tokens = queue.Queue()
87 | completion_tokens = queue.Queue()
88 |
89 | GGAt = threading.Thread(
90 | target=GoogleAI_Gemini_API_thread,
91 | args=(
92 | conversation,
93 | ans,
94 | prompt_tokens,
95 | completion_tokens,
96 | ),
97 | kwargs={
98 | "model_name":model_name,
99 | "max_output_tokens":max_output_tokens,
100 | "temperature":temperature,
101 | "retry":retry,
102 | },
103 | )
104 |
105 |
106 | GGAt.start()
107 | GGAt.join(timeout)
108 |
109 | if GGAt.is_alive():
110 | return None
111 |
112 | else:
113 | end_time = time.time()
114 | llm_result = ans.get()
115 | prompt_tokens = prompt_tokens.get()
116 | completion_tokens = completion_tokens.get()
117 | if command != "no_print":
118 | print("\nGoogleAI_Gemini_Answer ----------\n")
119 | print(f"Model: {model_name}")
120 | print(f"Duration: {end_time - start_time:.2f}s")
121 | print(f"Prompt tokens: {prompt_tokens}")
122 | print(f"Completion tokens: {completion_tokens}")
123 | print(f"Total tokens: {prompt_tokens+completion_tokens}\n")
124 | print(f"{chatQ}\n")
125 | print(f"Gemini Answer : {llm_result}")
126 | print("\n----------\n")
127 |
128 | cleaned_llm_result = "\n".join(line.strip() for line in llm_result.splitlines() if line.strip())
129 | return cleaned_llm_result
130 |
131 |
132 | def GoogleAI_Gemini_API_thread(
133 | conversation,
134 | ans,
135 | prompt_tokens,
136 | completion_tokens,
137 | model_name = "gemini-2.0-flash",
138 | max_output_tokens = 512,
139 | temperature = 0.9,
140 | retry = 3,
141 | ):
142 |
143 | def convert2gemini_conversation(conversation):
144 | system_contents = []
145 | new_conversation = []
146 | current_role = None
147 | current_parts = []
148 |
149 | for entry in conversation:
150 | if entry['role'] == 'system':
151 | system_contents.append(entry['content'])
152 | continue
153 |
154 | if entry['role'] == 'assistant':
155 | entry['role'] = 'model'
156 |
157 | if entry['role'] == current_role:
158 | current_parts.append(entry['content'])
159 | else:
160 | if current_role is not None:
161 | new_conversation.append({'role': current_role, 'parts': '\n'.join(current_parts)})
162 |
163 | current_role = entry['role']
164 | current_parts = [entry['content']]
165 |
166 | if current_role is not None:
167 | new_conversation.append({'role': current_role, 'parts': '\n'.join(current_parts)})
168 |
169 | system_text = '\n'.join(system_contents)
170 |
171 | return system_text, new_conversation
172 |
173 | system_instruction, conversation_for_gemini = convert2gemini_conversation(conversation)
174 |
175 | genai.configure(api_key=AIVT_Config.google_api_key)
176 |
177 | generation_config = genai.types.GenerationConfig(
178 | max_output_tokens=max_output_tokens,
179 | temperature=temperature,
180 | )
181 |
182 | safety_settings=[
183 | {
184 | "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
185 | "threshold": "BLOCK_NONE",
186 | },
187 | {
188 | "category": "HARM_CATEGORY_HATE_SPEECH",
189 | "threshold": "BLOCK_NONE",
190 | },
191 | {
192 | "category": "HARM_CATEGORY_HARASSMENT",
193 | "threshold": "BLOCK_NONE",
194 | },
195 | {
196 | "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
197 | "threshold": "BLOCK_NONE",
198 | }
199 | ]
200 |
201 | genimi_model = genai.GenerativeModel(
202 | system_instruction=system_instruction,
203 | model_name=model_name,
204 | generation_config=generation_config,
205 | safety_settings=safety_settings
206 | )
207 |
208 | reT = 0
209 | while reT < retry:
210 | reT += 1
211 |
212 | try:
213 | response = genimi_model.generate_content(conversation_for_gemini)
214 |
215 | try:
216 | message = response.text
217 | except Exception as e:
218 | print(f"\n{response.prompt_feedback}\n{e}\n")
219 | ans.put("")
220 | return
221 |
222 | response_dict = response.to_dict()
223 | usage_metadata = response_dict.get('usage_metadata', {})
224 | prompt_tokens.put(usage_metadata.get('prompt_token_count'))
225 | completion_tokens.put(usage_metadata.get('candidates_token_count'))
226 | ans.put(message)
227 | return
228 |
229 | except Exception as e:
230 | if reT < retry:
231 | print(f"!!! GoogleAI_Gemini_API retry {reT} time !!!\n{e}\n")
232 | continue
233 |
234 | else:
235 | print(f"!!! GoogleAI_Gemini_API retry {reT} time !!!\n{e}\n")
236 | ans.put("")
237 | return
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------
/Google/gemini/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/Google/gemini/__init__.py
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | Copyright (c) 2024 PKevin AI VTuber Developer
5 |
6 | 1. Definitions
7 | --------------
8 |
9 | 1.1. "Contributor"
10 | means each individual or legal entity that creates, contributes to
11 | the creation of, or owns Covered Software.
12 |
13 | 1.2. "Contributor Version"
14 | means the combination of the Contributions of others (if any) used
15 | by a Contributor and that particular Contributor's Contribution.
16 |
17 | 1.3. "Contribution"
18 | means Covered Software of a particular Contributor.
19 |
20 | 1.4. "Covered Software"
21 | means Source Code Form to which the initial Contributor has attached
22 | the notice in Exhibit A, the Executable Form of such Source Code
23 | Form, and Modifications of such Source Code Form, in each case
24 | including portions thereof.
25 |
26 | 1.5. "Incompatible With Secondary Licenses"
27 | means
28 |
29 | (a) that the initial Contributor has attached the notice described
30 | in Exhibit B to the Covered Software; or
31 |
32 | (b) that the Covered Software was made available under the terms of
33 | version 1.1 or earlier of the License, but not also under the
34 | terms of a Secondary License.
35 |
36 | 1.6. "Executable Form"
37 | means any form of the work other than Source Code Form.
38 |
39 | 1.7. "Larger Work"
40 | means a work that combines Covered Software with other material, in
41 | a separate file or files, that is not Covered Software.
42 |
43 | 1.8. "License"
44 | means this document.
45 |
46 | 1.9. "Licensable"
47 | means having the right to grant, to the maximum extent possible,
48 | whether at the time of the initial grant or subsequently, any and
49 | all of the rights conveyed by this License.
50 |
51 | 1.10. "Modifications"
52 | means any of the following:
53 |
54 | (a) any file in Source Code Form that results from an addition to,
55 | deletion from, or modification of the contents of Covered
56 | Software; or
57 |
58 | (b) any new file in Source Code Form that contains any Covered
59 | Software.
60 |
61 | 1.11. "Patent Claims" of a Contributor
62 | means any patent claim(s), including without limitation, method,
63 | process, and apparatus claims, in any patent Licensable by such
64 | Contributor that would be infringed, but for the grant of the
65 | License, by the making, using, selling, offering for sale, having
66 | made, import, or transfer of either its Contributions or its
67 | Contributor Version.
68 |
69 | 1.12. "Secondary License"
70 | means either the GNU General Public License, Version 2.0, the GNU
71 | Lesser General Public License, Version 2.1, the GNU Affero General
72 | Public License, Version 3.0, or any later versions of those
73 | licenses.
74 |
75 | 1.13. "Source Code Form"
76 | means the form of the work preferred for making modifications.
77 |
78 | 1.14. "You" (or "Your")
79 | means an individual or a legal entity exercising rights under this
80 | License. For legal entities, "You" includes any entity that
81 | controls, is controlled by, or is under common control with You. For
82 | purposes of this definition, "control" means (a) the power, direct
83 | or indirect, to cause the direction or management of such entity,
84 | whether by contract or otherwise, or (b) ownership of more than
85 | fifty percent (50%) of the outstanding shares or beneficial
86 | ownership of such entity.
87 |
88 | 2. License Grants and Conditions
89 | --------------------------------
90 |
91 | 2.1. Grants
92 |
93 | Each Contributor hereby grants You a world-wide, royalty-free,
94 | non-exclusive license:
95 |
96 | (a) under intellectual property rights (other than patent or trademark)
97 | Licensable by such Contributor to use, reproduce, make available,
98 | modify, display, perform, distribute, and otherwise exploit its
99 | Contributions, either on an unmodified basis, with Modifications, or
100 | as part of a Larger Work; and
101 |
102 | (b) under Patent Claims of such Contributor to make, use, sell, offer
103 | for sale, have made, import, and otherwise transfer either its
104 | Contributions or its Contributor Version.
105 |
106 | 2.2. Effective Date
107 |
108 | The licenses granted in Section 2.1 with respect to any Contribution
109 | become effective for each Contribution on the date the Contributor first
110 | distributes such Contribution.
111 |
112 | 2.3. Limitations on Grant Scope
113 |
114 | The licenses granted in this Section 2 are the only rights granted under
115 | this License. No additional rights or licenses will be implied from the
116 | distribution or licensing of Covered Software under this License.
117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
118 | Contributor:
119 |
120 | (a) for any code that a Contributor has removed from Covered Software;
121 | or
122 |
123 | (b) for infringements caused by: (i) Your and any other third party's
124 | modifications of Covered Software, or (ii) the combination of its
125 | Contributions with other software (except as part of its Contributor
126 | Version); or
127 |
128 | (c) under Patent Claims infringed by Covered Software in the absence of
129 | its Contributions.
130 |
131 | This License does not grant any rights in the trademarks, service marks,
132 | or logos of any Contributor (except as may be necessary to comply with
133 | the notice requirements in Section 3.4).
134 |
135 | 2.4. Subsequent Licenses
136 |
137 | No Contributor makes additional grants as a result of Your choice to
138 | distribute the Covered Software under a subsequent version of this
139 | License (see Section 10.2) or under the terms of a Secondary License (if
140 | permitted under the terms of Section 3.3).
141 |
142 | 2.5. Representation
143 |
144 | Each Contributor represents that the Contributor believes its
145 | Contributions are its original creation(s) or it has sufficient rights
146 | to grant the rights to its Contributions conveyed by this License.
147 |
148 | 2.6. Fair Use
149 |
150 | This License is not intended to limit any rights You have under
151 | applicable copyright doctrines of fair use, fair dealing, or other
152 | equivalents.
153 |
154 | 2.7. Conditions
155 |
156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
157 | in Section 2.1.
158 |
159 | 3. Responsibilities
160 | -------------------
161 |
162 | 3.1. Distribution of Source Form
163 |
164 | All distribution of Covered Software in Source Code Form, including any
165 | Modifications that You create or to which You contribute, must be under
166 | the terms of this License. You must inform recipients that the Source
167 | Code Form of the Covered Software is governed by the terms of this
168 | License, and how they can obtain a copy of this License. You may not
169 | attempt to alter or restrict the recipients' rights in the Source Code
170 | Form.
171 |
172 | 3.2. Distribution of Executable Form
173 |
174 | If You distribute Covered Software in Executable Form then:
175 |
176 | (a) such Covered Software must also be made available in Source Code
177 | Form, as described in Section 3.1, and You must inform recipients of
178 | the Executable Form how they can obtain a copy of such Source Code
179 | Form by reasonable means in a timely manner, at a charge no more
180 | than the cost of distribution to the recipient; and
181 |
182 | (b) You may distribute such Executable Form under the terms of this
183 | License, or sublicense it under different terms, provided that the
184 | license for the Executable Form does not attempt to limit or alter
185 | the recipients' rights in the Source Code Form under this License.
186 |
187 | 3.3. Distribution of a Larger Work
188 |
189 | You may create and distribute a Larger Work under terms of Your choice,
190 | provided that You also comply with the requirements of this License for
191 | the Covered Software. If the Larger Work is a combination of Covered
192 | Software with a work governed by one or more Secondary Licenses, and the
193 | Covered Software is not Incompatible With Secondary Licenses, this
194 | License permits You to additionally distribute such Covered Software
195 | under the terms of such Secondary License(s), so that the recipient of
196 | the Larger Work may, at their option, further distribute the Covered
197 | Software under the terms of either this License or such Secondary
198 | License(s).
199 |
200 | 3.4. Notices
201 |
202 | You may not remove or alter the substance of any license notices
203 | (including copyright notices, patent notices, disclaimers of warranty,
204 | or limitations of liability) contained within the Source Code Form of
205 | the Covered Software, except that You may alter any license notices to
206 | the extent required to remedy known factual inaccuracies.
207 |
208 | 3.5. Application of Additional Terms
209 |
210 | You may choose to offer, and to charge a fee for, warranty, support,
211 | indemnity or liability obligations to one or more recipients of Covered
212 | Software. However, You may do so only on Your own behalf, and not on
213 | behalf of any Contributor. You must make it absolutely clear that any
214 | such warranty, support, indemnity, or liability obligation is offered by
215 | You alone, and You hereby agree to indemnify every Contributor for any
216 | liability incurred by such Contributor as a result of warranty, support,
217 | indemnity or liability terms You offer. You may include additional
218 | disclaimers of warranty and limitations of liability specific to any
219 | jurisdiction.
220 |
221 | 4. Inability to Comply Due to Statute or Regulation
222 | ---------------------------------------------------
223 |
224 | If it is impossible for You to comply with any of the terms of this
225 | License with respect to some or all of the Covered Software due to
226 | statute, judicial order, or regulation then You must: (a) comply with
227 | the terms of this License to the maximum extent possible; and (b)
228 | describe the limitations and the code they affect. Such description must
229 | be placed in a text file included with all distributions of the Covered
230 | Software under this License. Except to the extent prohibited by statute
231 | or regulation, such description must be sufficiently detailed for a
232 | recipient of ordinary skill to be able to understand it.
233 |
234 | 5. Termination
235 | --------------
236 |
237 | 5.1. The rights granted under this License will terminate automatically
238 | if You fail to comply with any of its terms. However, if You become
239 | compliant, then the rights granted under this License from a particular
240 | Contributor are reinstated (a) provisionally, unless and until such
241 | Contributor explicitly and finally terminates Your grants, and (b) on an
242 | ongoing basis, if such Contributor fails to notify You of the
243 | non-compliance by some reasonable means prior to 60 days after You have
244 | come back into compliance. Moreover, Your grants from a particular
245 | Contributor are reinstated on an ongoing basis if such Contributor
246 | notifies You of the non-compliance by some reasonable means, this is the
247 | first time You have received notice of non-compliance with this License
248 | from such Contributor, and You become compliant prior to 30 days after
249 | Your receipt of the notice.
250 |
251 | 5.2. If You initiate litigation against any entity by asserting a patent
252 | infringement claim (excluding declaratory judgment actions,
253 | counter-claims, and cross-claims) alleging that a Contributor Version
254 | directly or indirectly infringes any patent, then the rights granted to
255 | You by any and all Contributors for the Covered Software under Section
256 | 2.1 of this License shall terminate.
257 |
258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
259 | end user license agreements (excluding distributors and resellers) which
260 | have been validly granted by You or Your distributors under this License
261 | prior to termination shall survive termination.
262 |
263 | ************************************************************************
264 | * *
265 | * 6. Disclaimer of Warranty *
266 | * ------------------------- *
267 | * *
268 | * Covered Software is provided under this License on an "as is" *
269 | * basis, without warranty of any kind, either expressed, implied, or *
270 | * statutory, including, without limitation, warranties that the *
271 | * Covered Software is free of defects, merchantable, fit for a *
272 | * particular purpose or non-infringing. The entire risk as to the *
273 | * quality and performance of the Covered Software is with You. *
274 | * Should any Covered Software prove defective in any respect, You *
275 | * (not any Contributor) assume the cost of any necessary servicing, *
276 | * repair, or correction. This disclaimer of warranty constitutes an *
277 | * essential part of this License. No use of any Covered Software is *
278 | * authorized under this License except under this disclaimer. *
279 | * *
280 | ************************************************************************
281 |
282 | ************************************************************************
283 | * *
284 | * 7. Limitation of Liability *
285 | * -------------------------- *
286 | * *
287 | * Under no circumstances and under no legal theory, whether tort *
288 | * (including negligence), contract, or otherwise, shall any *
289 | * Contributor, or anyone who distributes Covered Software as *
290 | * permitted above, be liable to You for any direct, indirect, *
291 | * special, incidental, or consequential damages of any character *
292 | * including, without limitation, damages for lost profits, loss of *
293 | * goodwill, work stoppage, computer failure or malfunction, or any *
294 | * and all other commercial damages or losses, even if such party *
295 | * shall have been informed of the possibility of such damages. This *
296 | * limitation of liability shall not apply to liability for death or *
297 | * personal injury resulting from such party's negligence to the *
298 | * extent applicable law prohibits such limitation. Some *
299 | * jurisdictions do not allow the exclusion or limitation of *
300 | * incidental or consequential damages, so this exclusion and *
301 | * limitation may not apply to You. *
302 | * *
303 | ************************************************************************
304 |
305 | 8. Litigation
306 | -------------
307 |
308 | Any litigation relating to this License may be brought only in the
309 | courts of a jurisdiction where the defendant maintains its principal
310 | place of business and such litigation shall be governed by laws of that
311 | jurisdiction, without reference to its conflict-of-law provisions.
312 | Nothing in this Section shall prevent a party's ability to bring
313 | cross-claims or counter-claims.
314 |
315 | 9. Miscellaneous
316 | ----------------
317 |
318 | This License represents the complete agreement concerning the subject
319 | matter hereof. If any provision of this License is held to be
320 | unenforceable, such provision shall be reformed only to the extent
321 | necessary to make it enforceable. Any law or regulation which provides
322 | that the language of a contract shall be construed against the drafter
323 | shall not be used to construe this License against a Contributor.
324 |
325 | 10. Versions of the License
326 | ---------------------------
327 |
328 | 10.1. New Versions
329 |
330 | Mozilla Foundation is the license steward. Except as provided in Section
331 | 10.3, no one other than the license steward has the right to modify or
332 | publish new versions of this License. Each version will be given a
333 | distinguishing version number.
334 |
335 | 10.2. Effect of New Versions
336 |
337 | You may distribute the Covered Software under the terms of the version
338 | of the License under which You originally received the Covered Software,
339 | or under the terms of any subsequent version published by the license
340 | steward.
341 |
342 | 10.3. Modified Versions
343 |
344 | If you create software not governed by this License, and you want to
345 | create a new license for such software, you may create and use a
346 | modified version of this License if you rename the license and remove
347 | any references to the name of the license steward (except to note that
348 | such modified license differs from this License).
349 |
350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
351 | Licenses
352 |
353 | If You choose to distribute Source Code Form that is Incompatible With
354 | Secondary Licenses under the terms of this version of the License, the
355 | notice described in Exhibit B of this License must be attached.
356 |
357 | Exhibit A - Source Code Form License Notice
358 | -------------------------------------------
359 |
360 | This Source Code Form is subject to the terms of the Mozilla Public
361 | License, v. 2.0. If a copy of the MPL was not distributed with this
362 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
363 |
364 | Copyright (c) 2024 PKevin AI VTuber Developer
365 |
366 | If it is not possible or desirable to put the notice in a particular
367 | file, then You may include the notice in a location (such as a LICENSE
368 | file in a relevant directory) where a recipient would be likely to look
369 | for such a notice.
370 |
371 | You may add additional accurate notices of copyright ownership.
372 |
373 | Exhibit B - "Incompatible With Secondary Licenses" Notice
374 | ---------------------------------------------------------
375 |
376 | This Source Code Form is "Incompatible With Secondary Licenses", as
377 | defined by the Mozilla Public License, v. 2.0.
378 |
--------------------------------------------------------------------------------
/Live_Chat/Live_Chat.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 | import time
5 | import threading
6 | import queue
7 | import random
8 |
9 | import pytchat
10 | from twitchio.ext import commands as twitch_commands
11 |
12 | import AIVT_Config
13 | import AI_Vtuber_UI as aivtui
14 | import My_Tools.Token_Calculator as tokenC
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Live_Chat_Status = {
26 | "YouTube_live_chat": False,
27 | "YouTube_live_chat_alive": False,
28 | "YouTube_live_chat_retry": False,
29 | "YouTube_live_chat_connect_fail_count": 0,
30 | "Twitch_live_chat": False,
31 | "llm_request_checker": False,
32 | }
33 |
34 | live_chat_status = {"Twitch_live_chat": False,}
35 |
36 | Live_chat_parameters = {
37 | "yt_channel_name": "AI VTuber Dev PKevin",
38 | "yt_live_id": "qTmJ4YkziEk",
39 | "yt_live_chat_vip_names": [],
40 | "yt_live_chat_ban_names": [],
41 | "yt_live_chat_log": True,
42 | "yt_live_chat_read_chat_now": False,
43 | "yt_response_chatroom": False,
44 | "yt_response_owner": False,
45 | "yt_response_vip": False,
46 | "yt_response_individual": False,
47 | "yt_wait_list_max": 10,
48 | "yt_chat_max_tokens": 128,
49 | "yt_chat_max_response": 5,
50 |
51 | "tw_channel_name": "pkevin_ai_vtuber_dev",
52 | "tw_live_chat_vip_names": [],
53 | "tw_live_chat_ban_names": [],
54 | "tw_live_chat_log": True,
55 | "tw_live_chat_read_chat_now": False,
56 | "tw_response_chatroom": False,
57 | "tw_response_owner": False,
58 | "tw_response_vip": False,
59 | "tw_response_individual": False,
60 | "tw_wait_list_max": 10,
61 | "tw_chat_max_tokens": 128,
62 | "tw_chat_max_response": 5,
63 | }
64 |
65 | YT_LC_wait_list = []
66 | TW_LC_raw_list = []
67 | TW_LC_wait_list = []
68 | Live_Chat_LLM_wait_list = []
69 |
70 |
71 |
72 |
73 |
74 | def YouTube_live_chat_connect():
75 | global Live_Chat_Status, Live_chat_parameters, YT_live_chat
76 |
77 | try:
78 | YT_live_chat = pytchat.create(video_id=Live_chat_parameters["yt_live_id"])
79 | print(f"!!! YouTube Live ID: {Live_chat_parameters['yt_live_id']} Connect Success !!!\n")
80 | Live_Chat_Status["YouTube_live_chat"] = True
81 | Live_Chat_Status["YouTube_live_chat_alive"] = True
82 | Live_Chat_Status["YouTube_live_chat_connect_fail_count"] = 0
83 |
84 | except Exception as e:
85 | Live_Chat_Status["YouTube_live_chat_connect_fail_count"] += 1
86 |
87 | if Live_Chat_Status["YouTube_live_chat_connect_fail_count"] >= 50:
88 | print(f"!!! YouTube Live ID: {Live_chat_parameters['yt_live_id']} Connect Failed !!!{e}\n")
89 | Live_Chat_Status["YouTube_live_chat"] = False
90 | Live_Chat_Status["YouTube_live_chat_alive"] = False
91 | Live_Chat_Status["YouTube_live_chat_connect_fail_count"] = 0
92 |
93 |
94 | def YouTube_live_chat_get_comments():
95 | global Live_Chat_Status, Live_chat_parameters, YT_live_chat, YT_LC_wait_list
96 |
97 | YT_LC_wait_list = []
98 |
99 | while Live_Chat_Status["YouTube_live_chat"]:
100 | while YT_live_chat.is_alive() and Live_Chat_Status["YouTube_live_chat"]:
101 | time.sleep(0.1)
102 |
103 | try:
104 | chat_data = YT_live_chat.get()
105 | chat_comments = list(chat_data.sync_items())
106 |
107 | if chat_data and chat_comments:
108 | chat_list = []
109 |
110 | for c in chat_comments:
111 | chat_author = c.author.name
112 | chat_raw = c.message
113 |
114 | if chat_author in Live_chat_parameters["yt_live_chat_ban_names"]:
115 | continue
116 |
117 | elif (c.author.isChatOwner and Live_chat_parameters["yt_response_owner"]) or chat_author in Live_chat_parameters["yt_live_chat_vip_names"]:
118 | chat_now = {chat_author: chat_raw}
119 | chat_list.append(chat_now)
120 |
121 | elif tokenC.num_tokens_from_conversation([{"role": "user", "content": chat_raw}], "gpt-3.5-turbo")-9 < Live_chat_parameters["yt_chat_max_tokens"]:
122 | chat_now = {chat_author: chat_raw}
123 | chat_list.append(chat_now)
124 |
125 | else:
126 | if Live_chat_parameters["yt_live_chat_log"]:
127 | print(f"!!! *{chat_author}* comment beyond max tokens - YouTube Live Chat !!!")
128 |
129 | if chat_list:
130 | YT_LC_wait_list.extend(chat_list)
131 | if Live_chat_parameters["yt_live_chat_log"]:
132 | chat_c = "\n\n".join([f"{key} : {d[key]}" for d in chat_list for key in d])
133 | print(f"\nYouTube Live Chat ----------\n\n{chat_c}\n\n----------\n")
134 |
135 | except Exception as e:
136 | print(f"YouTube get livechat fail:\n{e}\n")
137 | break
138 |
139 |
140 | print(f"YouTube Live Chat Reonnecting...")
141 | Live_Chat_Status["YouTube_live_chat_alive"] = False
142 | while not Live_Chat_Status["YouTube_live_chat_alive"]:
143 | time.sleep(0.01)
144 |
145 | Live_Chat_Status["YouTube_live_chat"] = False
146 |
147 | def YouTube_live_chat_pick_comments():
148 | global Live_Chat_Status, Live_chat_parameters, YT_LC_wait_list, Live_Chat_LLM_wait_list
149 |
150 | while Live_Chat_Status["YouTube_live_chat"]:
151 | if Live_chat_parameters["yt_response_chatroom"] and YT_LC_wait_list:
152 | c = len(YT_LC_wait_list)
153 | chat_list = []
154 | while c > 0:
155 | c -= 1
156 | chat_list.append(YT_LC_wait_list.pop(0))
157 |
158 | chat_list.reverse()
159 |
160 | owner_name = Live_chat_parameters["yt_channel_name"]
161 | owner_chat_now = None
162 |
163 | if Live_chat_parameters["yt_response_owner"] and chat_list:
164 | if any(owner_name in d for d in chat_list):
165 | for d in chat_list:
166 | if owner_name in d:
167 | owner_chat_now = d
168 | break
169 |
170 | chat_list = [d for d in chat_list if owner_name not in d]
171 |
172 | name = list(owner_chat_now.keys())[0]
173 | chat_now = owner_chat_now[name]
174 | Live_Chat_LLM_wait_list.append({"role": "youtube_chat", "content": f"{name} : {chat_now}"})
175 |
176 | else:
177 | chat_list = [d for d in chat_list if owner_name not in d]
178 |
179 |
180 | vip_names = Live_chat_parameters["yt_live_chat_vip_names"]
181 | vip_chat_now = None
182 |
183 | if Live_chat_parameters["yt_response_vip"] and chat_list:
184 | for vip_name in vip_names:
185 | if any(vip_name in d for d in chat_list):
186 | for d in chat_list:
187 | if vip_name in d:
188 | vip_chat_now = d
189 | break
190 |
191 | chat_list = [d for d in chat_list if vip_name not in d]
192 |
193 | name = list(vip_chat_now.keys())[0]
194 | chat_now = vip_chat_now[name]
195 | Live_Chat_LLM_wait_list.append({"role": "youtube_chat", "content": f"{name} : {chat_now}"})
196 |
197 |
198 | if chat_list:
199 | first_occurrences = {}
200 | for d in chat_list:
201 | key = next(iter(d))
202 | if key not in first_occurrences:
203 | first_occurrences[key] = d
204 |
205 | keys_to_pick = list(first_occurrences.keys())
206 | random_keys = random.sample(keys_to_pick, min(Live_chat_parameters["yt_chat_max_response"], len(keys_to_pick)))
207 | selected_comments = [first_occurrences[key] for key in random_keys]
208 |
209 | if Live_chat_parameters["yt_response_individual"]:
210 | for chat_content in selected_comments:
211 | name = list(chat_content.keys())[0]
212 | chat_now = chat_content[name]
213 | Live_Chat_LLM_wait_list.append({"role": "youtube_chat", "content": f"{name} : {chat_now}"})
214 |
215 | else:
216 | chat_s = "\n".join([f"{key} : {d[key]}" for d in selected_comments for key in d])
217 | Live_Chat_LLM_wait_list.append({"role": "youtube_chat", "content": chat_s})
218 |
219 | time.sleep(0.1)
220 |
221 |
222 |
223 |
224 |
225 | def Twitch_live_chat_connect(live_chat_status, tw_lc_raw_list):
226 | try:
227 | tlc = Twitch_live_chat(
228 | channel=AIVT_Config.Twitch_user_name,
229 | token=AIVT_Config.Twitch_token,
230 | tw_lc_raw_list=tw_lc_raw_list
231 | )
232 | tlc.run()
233 |
234 | while live_chat_status['Twitch_live_chat']:
235 | time.sleep(0.1)
236 |
237 | tlc.close()
238 |
239 | except Exception as e:
240 | print(f"!!! Twitch {AIVT_Config.Twitch_user_name} Disconnected !!!\n\n{e}\n\n")
241 | live_chat_status['Twitch_live_chat'] = False
242 |
243 | class Twitch_live_chat(twitch_commands.Bot):
244 | def __init__(self, channel, token, tw_lc_raw_list):
245 | super().__init__(token=token, prefix='!', initial_channels=[channel])
246 | self.tw_lc_raw_list = tw_lc_raw_list
247 |
248 | async def event_ready(self):
249 | print(f"!!! Twitch {self.nick} Connected !!!\n")
250 |
251 | async def event_message(self, message):
252 | if message.echo:
253 | return
254 |
255 | formatted_message = f"{message.author.name} : {message.content}"
256 | self.tw_lc_raw_list.append(formatted_message)
257 |
258 | await self.handle_commands(message)
259 |
260 |
261 | def Twitch_live_chat_get_comments():
262 | global Live_Chat_Status, Live_chat_parameters, TW_LC_raw_list, TW_LC_wait_list
263 |
264 | TW_LC_wait_list = []
265 |
266 | while Live_Chat_Status["Twitch_live_chat"]:
267 | time.sleep(0.1)
268 |
269 | try:
270 | if TW_LC_raw_list:
271 | chat_list = []
272 |
273 | TW_LC_raw_list_l = len(TW_LC_raw_list)
274 | while TW_LC_raw_list_l > 0:
275 | TW_LC_raw_list_l -= 1
276 |
277 | chat_author, chat_raw = TW_LC_raw_list.pop(0).split(" : ", 1)
278 |
279 | if chat_author in Live_chat_parameters["tw_live_chat_ban_names"]:
280 | continue
281 |
282 | elif (chat_author == Live_chat_parameters["tw_channel_name"] and Live_chat_parameters["tw_response_owner"]) or chat_author in Live_chat_parameters["tw_live_chat_vip_names"]:
283 | chat_now = {chat_author: chat_raw}
284 | chat_list.append(chat_now)
285 |
286 | elif tokenC.num_tokens_from_conversation([{"role": "user", "content": chat_raw}], "gpt-3.5-turbo")-9 < Live_chat_parameters["tw_chat_max_tokens"]:
287 | chat_now = {chat_author: chat_raw}
288 | chat_list.append(chat_now)
289 |
290 | else:
291 | if Live_chat_parameters["tw_live_chat_log"]:
292 | print(f"!!! *{chat_author}* comment beyond max tokens - Twitch Live Chat !!!")
293 |
294 | if chat_list:
295 | TW_LC_wait_list.extend(chat_list)
296 | if Live_chat_parameters["tw_live_chat_log"]:
297 | chat_c = "\n\n".join([f"{key} : {d[key]}" for d in chat_list for key in d])
298 | print(f"\nTwitch Live Chat ----------\n\n{chat_c}\n\n----------\n")
299 |
300 | except:
301 | '''
302 | Never gonna give you up
303 | Never gonna let you down
304 | Never gonna run around and desert you
305 | Never gonna make you cry
306 | Never gonna say goodbye
307 | Never gonna tell a lie and hurt you
308 | '''
309 |
310 | def Twitch_live_chat_pick_comments():
311 | global Live_Chat_Status, Live_chat_parameters, TW_LC_wait_list, Live_Chat_LLM_wait_list
312 |
313 | while Live_Chat_Status["Twitch_live_chat"]:
314 | if Live_chat_parameters["tw_response_chatroom"] and TW_LC_wait_list:
315 | c = len(TW_LC_wait_list)
316 | chat_list = []
317 | while c > 0:
318 | c -= 1
319 | chat_list.append(TW_LC_wait_list.pop(0))
320 |
321 | chat_list.reverse()
322 |
323 | owner_name = Live_chat_parameters["tw_channel_name"]
324 | owner_chat_now = None
325 |
326 | if Live_chat_parameters["tw_response_owner"] and chat_list:
327 | if any(owner_name in d for d in chat_list):
328 | for d in chat_list:
329 | if owner_name in d:
330 | owner_chat_now = d
331 | break
332 |
333 | chat_list = [d for d in chat_list if owner_name not in d]
334 |
335 | name = list(owner_chat_now.keys())[0]
336 | chat_now = owner_chat_now[name]
337 | Live_Chat_LLM_wait_list.append({"role": "twitch_chat", "content": f"{name} : {chat_now}"})
338 |
339 | else:
340 | chat_list = [d for d in chat_list if owner_name not in d]
341 |
342 | vip_names = Live_chat_parameters["tw_live_chat_vip_names"]
343 | vip_chat_now = None
344 |
345 | if Live_chat_parameters["tw_response_vip"] and chat_list:
346 | for vip_name in vip_names:
347 | if any(vip_name in d for d in chat_list):
348 | for d in chat_list:
349 | if vip_name in d:
350 | vip_chat_now = d
351 | break
352 |
353 | chat_list = [d for d in chat_list if vip_name not in d]
354 |
355 | name = list(vip_chat_now.keys())[0]
356 | chat_now = vip_chat_now[name]
357 | Live_Chat_LLM_wait_list.append({"role": "twitch_chat", "content": f"{name} : {chat_now}"})
358 |
359 | if chat_list:
360 | first_occurrences = {}
361 | for d in chat_list:
362 | key = next(iter(d))
363 | if key not in first_occurrences:
364 | first_occurrences[key] = d
365 |
366 | keys_to_pick = list(first_occurrences.keys())
367 | random_keys = random.sample(keys_to_pick, min(Live_chat_parameters["tw_chat_max_response"], len(keys_to_pick)))
368 | selected_comments = [first_occurrences[key] for key in random_keys]
369 |
370 | if Live_chat_parameters["tw_response_individual"]:
371 | for chat_content in selected_comments:
372 | name = list(chat_content.keys())[0]
373 | chat_now = chat_content[name]
374 | Live_Chat_LLM_wait_list.append({"role": "twitch_chat", "content": f"{name} : {chat_now}"})
375 |
376 | else:
377 | chat_s = "\n".join([f"{key} : {d[key]}" for d in selected_comments for key in d])
378 | Live_Chat_LLM_wait_list.append({"role": "twitch_chat", "content": chat_s})
379 |
380 | time.sleep(0.1)
381 |
382 |
383 |
384 |
385 |
386 | def YouTube_LiveChat_boot_on():
387 | global Live_Chat_Status
388 |
389 | YT_lcgc = threading.Thread(target=YouTube_live_chat_get_comments)
390 | YT_lcgc.start()
391 |
392 | YT_lcpc = threading.Thread(target=YouTube_live_chat_pick_comments)
393 | YT_lcpc.start()
394 |
395 | if not Live_Chat_Status["llm_request_checker"]:
396 | Live_Chat_Status["llm_request_checker"] = True
397 | LC_llmc = threading.Thread(target=Live_Chat_LLM_checker)
398 | LC_llmc.start()
399 |
400 |
401 | def Twitch_LiveChat_boot_on():
402 | global Live_Chat_Status
403 |
404 | TW_lcgc = threading.Thread(target=Twitch_live_chat_get_comments)
405 | TW_lcgc.start()
406 |
407 | TW_lcpc = threading.Thread(target=Twitch_live_chat_pick_comments)
408 | TW_lcpc.start()
409 |
410 | if not Live_Chat_Status["llm_request_checker"]:
411 | Live_Chat_Status["llm_request_checker"] = True
412 | LC_llmc = threading.Thread(target=Live_Chat_LLM_checker)
413 | LC_llmc.start()
414 |
415 |
416 |
417 |
418 |
419 | def Live_Chat_LLM_checker():
420 | global Live_Chat_Status, Live_chat_parameters, Live_Chat_LLM_wait_list
421 |
422 | Live_Chat_LLM_wait_list = []
423 |
424 | while Live_Chat_Status["YouTube_live_chat"] or Live_Chat_Status["Twitch_live_chat"]:
425 | if Live_Chat_LLM_wait_list:
426 | ans_requst = Live_Chat_LLM_wait_list.pop(0)
427 | chat_role = ans_requst["role"]
428 | chat_now = ans_requst["content"]
429 | ai_name = aivtui.AIVT_Using_character
430 |
431 | llm_ans = queue.Queue()
432 |
433 | llm_rt = threading.Thread(target=aivtui.LLM_Request_thread, args=(ans_requst, llm_ans, ))
434 | llm_rt.start()
435 | llm_rt.join()
436 |
437 | ai_ans = llm_ans.get()
438 |
439 | if ai_ans != "":
440 | aivtui.GUI_AIVT_Speaking_wait_list.append(
441 | {
442 | "chat_role": chat_role,
443 | "chat_now": chat_now,
444 | "ai_ans": ai_ans,
445 | "ai_name": ai_name,
446 | }
447 | )
448 |
449 | time.sleep(0.1)
450 |
451 | Live_Chat_Status["llm_request_checker"] = False
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 | if __name__ == "__main__":
463 | Live_Chat_Status["YouTube_live_chat"] = True
464 | Live_chat_parameters["yt_live_id"] = "qTmJ4YkziEk"
465 | Live_chat_parameters["yt_response_chatroom"] = True
466 | Live_chat_parameters["yt_response_owner"] = True
467 | Live_chat_parameters["yt_response_vip"] = True
468 | YouTube_live_chat_connect()
469 | YouTube_LiveChat_boot_on()
470 |
471 |
472 |
473 |
474 |
--------------------------------------------------------------------------------
/Live_Chat/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/Live_Chat/__init__.py
--------------------------------------------------------------------------------
/Mic_Record.py:
--------------------------------------------------------------------------------
1 | import time
2 | import threading
3 | import pyaudio
4 | import wave
5 |
6 | import keyboard
7 |
8 | import AI_Vtuber_UI as aivtui
9 | from My_Tools.AIVT_print import aprint
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | user_mic_status = {
21 | "mic_on": False,
22 | "mic_record_running": False,
23 | "mic_checker_running": False,
24 | "mic_hotkeys_using": False,
25 | "mic_hotkey_1_detecting": False,
26 | "mic_hotkey_1_using": False,
27 | "mic_hotkey_1": "`",
28 | "mic_hotkey_2_detecting": False,
29 | "mic_hotkey_2_using": False,
30 | "mic_hotkey_2": "caps lock",
31 | }
32 |
33 | User_Mic_parameters = {
34 | "user_mic_audio_path": "Audio/user_mic_record/input_user_mic.wav",
35 | "input_device_name": "",
36 | "channels": 1,
37 | "format": pyaudio.paInt16,
38 | "rate": 24000,
39 | "chunk": 1024,
40 | "minimum_duration": 1,
41 | }
42 |
43 | Audio_frames_out = []
44 | Mic_hotkey_pressed = False
45 |
46 |
47 |
48 | def MC_Record():
49 | global user_mic_status, User_Mic_parameters, Mic_hotkey_pressed, Audio_frames_out
50 |
51 | input_device_index = Get_available_input_devices_ID(User_Mic_parameters["input_device_name"])
52 | CHUNK = User_Mic_parameters["chunk"]
53 |
54 | p = pyaudio.PyAudio()
55 | audio_stream = p.open(
56 | input=True,
57 | input_device_index=input_device_index,
58 | channels=User_Mic_parameters["channels"],
59 | format=User_Mic_parameters["format"],
60 | rate=User_Mic_parameters["rate"],
61 | frames_per_buffer=CHUNK,
62 | )
63 |
64 | Mic_hotkey_pressed = False
65 | dmhp = threading.Thread(target=Detect_Mic_hotkey_pressed)
66 | dmhp.start()
67 |
68 | while user_mic_status["mic_on"]:
69 | if Mic_hotkey_pressed:
70 | frames = []
71 | start_time = time.time()
72 | current_time = start_time
73 |
74 | while Mic_hotkey_pressed:
75 | data = audio_stream.read(CHUNK)
76 | frames.append(data)
77 | current_time = time.time()
78 |
79 | if current_time - start_time >= User_Mic_parameters["minimum_duration"]:
80 | Audio_frames_out.append(frames)
81 | aprint("* Record output *")
82 |
83 | else:
84 | aprint("*** Mic Record Cancel ***")
85 |
86 | time.sleep(0.1)
87 |
88 | user_mic_status["mic_record_running"] = False
89 |
90 | audio_stream.stop_stream()
91 | audio_stream.close()
92 | p.terminate()
93 | dmhp.join()
94 | print("* User Mic OFF *")
95 |
96 |
97 | def Detect_Mic_hotkey_pressed():
98 | global user_mic_status, Mic_hotkey_pressed
99 | def on_key_event(e):
100 | global Mic_hotkey_pressed
101 | if e.event_type == keyboard.KEY_DOWN and user_mic_status["mic_hotkeys_using"]:
102 | if user_mic_status["mic_hotkey_1_using"] and user_mic_status["mic_hotkey_2_using"]:
103 | hotkeys = f"{user_mic_status['mic_hotkey_1']}+{user_mic_status['mic_hotkey_2']}"
104 |
105 | elif user_mic_status["mic_hotkey_1_using"]:
106 | hotkeys = user_mic_status['mic_hotkey_1']
107 |
108 | else:
109 | hotkeys = user_mic_status['mic_hotkey_2']
110 |
111 | if not Mic_hotkey_pressed and keyboard.is_pressed(hotkeys):
112 | aprint('** Start Mic Recording **')
113 | Mic_hotkey_pressed = True
114 |
115 | elif e.event_type == keyboard.KEY_UP and user_mic_status["mic_hotkeys_using"]:
116 | if Mic_hotkey_pressed and ((e.name == user_mic_status["mic_hotkey_1"] and user_mic_status["mic_hotkey_1_using"]) or (e.name == user_mic_status["mic_hotkey_2"] and user_mic_status["mic_hotkey_2_using"])):
117 | aprint('** End Mic Recording **')
118 | Mic_hotkey_pressed = False
119 |
120 |
121 | keyboard.hook(on_key_event)
122 |
123 | print("* User Mic ON *")
124 | user_mic_status["mic_record_running"] = True
125 |
126 | while user_mic_status["mic_on"]:
127 | time.sleep(0.1)
128 |
129 | Mic_hotkey_pressed = False
130 |
131 | keyboard.unhook_all()
132 |
133 |
134 | def MC_Output_checker():
135 | global user_mic_status, Audio_frames_out
136 |
137 | user_mic_status["mic_checker_running"] = True
138 |
139 | while user_mic_status["mic_on"]:
140 | if Audio_frames_out:
141 | aprint("* output check *")
142 | mco = threading.Thread(target=aivtui.OpenAI_Whisper_thread, args=(Audio_frames_out.pop(0), ))
143 | mco.start()
144 |
145 | time.sleep(0.1)
146 |
147 | # Avoid mic off when MC_Record is still processing
148 | while user_mic_status["mic_record_running"]:
149 | time.sleep(0.1)
150 |
151 | if Audio_frames_out:
152 | aprint("* output check *")
153 | mco = threading.Thread(target=aivtui.OpenAI_Whisper_thread, args=(Audio_frames_out.pop(0), ))
154 | mco.start()
155 | mco.join()
156 |
157 | user_mic_status["mic_checker_running"] = False
158 |
159 |
160 |
161 |
162 |
163 | def save_audio2wav(audio_frames, save_path):
164 | global User_Mic_parameters
165 |
166 | p = pyaudio.PyAudio()
167 |
168 | wf = wave.open(save_path, 'wb')
169 | wf.setnchannels(User_Mic_parameters["channels"])
170 | wf.setsampwidth(p.get_sample_size(User_Mic_parameters["format"]))
171 | wf.setframerate(User_Mic_parameters["rate"])
172 | wf.writeframes(b''.join(audio_frames))
173 | wf.close()
174 |
175 |
176 |
177 |
178 |
179 | def Get_key_press():
180 | print("User Mic Hotkey Detecting(Press ESC to cancel)...")
181 | event = keyboard.read_event()
182 |
183 | if event.event_type == keyboard.KEY_DOWN:
184 | key_name = event.name
185 | if not key_name == "esc":
186 | print(f"Detected Key: {key_name}")
187 |
188 | else:
189 | print("User Mic Hotkey Cancel")
190 |
191 | return key_name
192 |
193 |
194 |
195 |
196 |
197 | def Available_Input_Device():
198 | p = pyaudio.PyAudio()
199 | info = p.get_host_api_info_by_index(0)
200 | numdevices = info.get('deviceCount')
201 |
202 | for i in range(0, numdevices):
203 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
204 | print(f"Input Device id {i} - {p.get_device_info_by_host_api_device_index(0, i).get('name')}")
205 |
206 | p.terminate()
207 |
208 |
209 | def Get_available_input_devices_List():
210 | p = pyaudio.PyAudio()
211 | info = p.get_host_api_info_by_index(0)
212 | numdevices = info.get('deviceCount')
213 | input_devices_list = []
214 | for i in range(0, numdevices):
215 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
216 | input_devices_list.append(p.get_device_info_by_host_api_device_index(0, i).get('name'))
217 |
218 | p.terminate()
219 | return input_devices_list
220 |
221 |
222 | def Get_available_input_devices_ID(devices_name):
223 | p = pyaudio.PyAudio()
224 | info = p.get_host_api_info_by_index(0)
225 | numdevices = info.get('deviceCount')
226 |
227 | # find input device id by device name
228 | for i in range(0, numdevices):
229 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
230 | if devices_name == p.get_device_info_by_host_api_device_index(0, i).get('name'):
231 | p.terminate()
232 | return i
233 |
234 | # if the name is not in input device list
235 | #return default system audio input device
236 | for i in range(0, numdevices):
237 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
238 | p.terminate()
239 | return i
240 |
241 | p.terminate()
242 | return None
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 | if __name__ == "__main__":
254 | Available_Input_Device()
255 | devices_id = Get_available_input_devices_ID("Never gonna make you cry never gonna say goodbye")
256 | print(f"Devices ID: {devices_id}")
257 | #Get_key_press()
258 |
259 |
260 |
--------------------------------------------------------------------------------
/My_Tools/AIVT_print.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 | import inspect
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | def aprint(print_content, start=""):
16 | print(print_content)
17 | '''
18 | filename = os.path.basename(sys.argv[0])
19 | current_frame = inspect.currentframe()
20 | caller_frame = inspect.getouterframes(current_frame, 2)
21 | function_name = caller_frame[1][3]
22 | print(f"{start}{filename} | {function_name} - {print_content}")
23 | '''
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/My_Tools/Token_Calculator.py:
--------------------------------------------------------------------------------
1 | import tiktoken
2 |
3 | def num_tokens_from_conversation(messages, model):
4 | try:
5 | encoding = tiktoken.encoding_for_model(model)
6 |
7 | except:
8 | encoding = tiktoken.get_encoding("cl100k_base")
9 |
10 |
11 | if model == "gpt-3.5-turbo":
12 | tokens_per_message = 4
13 | tokens_per_name = -1
14 |
15 | elif model == "gpt-4" or model == "gpt-4-turbo-preview":
16 | tokens_per_message = 3
17 | tokens_per_name = 1
18 |
19 | else:
20 | tokens_per_message = 4
21 | tokens_per_name = -1
22 |
23 |
24 | num_tokens = 0
25 | for message in messages:
26 | num_tokens += tokens_per_message
27 |
28 | for key, value in message.items():
29 | num_tokens += len(encoding.encode(value))
30 |
31 | if key == "name":
32 | num_tokens += tokens_per_name
33 |
34 | num_tokens += 3
35 | return num_tokens
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/My_Tools/Token_Calculator_GUI.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd..
3 | GUI_control_panel\runtime\python.exe My_Tools\Token_Calculator_GUI.py
--------------------------------------------------------------------------------
/My_Tools/Token_Calculator_GUI.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 |
5 | import tkinter as tk
6 | from tkinter import font
7 | from tkinter import ttk
8 |
9 | import My_Tools.Token_Calculator as tokenC
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | def calculate_tokens(text):
21 | con = [{"role": "user", "content": text}]
22 | tokens = tokenC.num_tokens_from_conversation(con, "gpt-3.5-turbo")
23 | return tokens-9
24 |
25 |
26 | class TokenCalculatorApp(tk.Tk):
27 | def __init__(self):
28 | super().__init__()
29 |
30 | self.title('Token 計算器')
31 | self.configure(bg='black')
32 |
33 | self.text_font = tk.StringVar(value="Arial")
34 | self.text_size = tk.IntVar(value=14) # 文字大小的變量
35 |
36 | self.init_ui()
37 |
38 | def init_ui(self):
39 | control_frame = ttk.Frame(self)
40 | control_frame.pack(fill=tk.X, padx=10, pady=5)
41 |
42 | # 創建字體選擇下拉菜單
43 | font_selector = ttk.Combobox(control_frame, textvariable=self.text_font, values=font.families(), width=20)
44 | font_selector.grid(row=0, column=0, padx=5, pady=5)
45 | font_selector.bind('<>', self.update_text_font)
46 |
47 | # 創建調整文字大小的 Spinbox
48 | size_spinbox = ttk.Spinbox(control_frame, from_=2, to=128, increment=2, textvariable=self.text_size, command=self.update_text_size, width=7)
49 | size_spinbox.grid(row=0, column=1, padx=5, pady=5)
50 |
51 | # 創建計算tokens的按鈕
52 | calculate_button = tk.Button(control_frame, text="Count it!", command=self.calculate_and_display_tokens, bg="grey", fg="white")
53 | calculate_button.grid(row=0, column=2, padx=5, pady=5)
54 |
55 | # 創建顯示tokens的標籤
56 | self.tokens_label = tk.Label(self, text="Tokens: 0", bg="black", fg="white", font=("Arial", 20))
57 | self.tokens_label.pack(padx=10, pady=5)
58 |
59 | # 文字輸入框外層的滾動條
60 | text_frame = ttk.Frame(self)
61 | text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
62 | self.text_scroll = tk.Scrollbar(text_frame)
63 | self.text_scroll.pack(side=tk.RIGHT, fill=tk.Y)
64 |
65 | # 創建文本輸入框,並與滾動條連接
66 | self.text_input = tk.Text(text_frame, bg="grey", fg="white",
67 | font=(self.text_font.get(), self.text_size.get()),
68 | yscrollcommand=self.text_scroll.set)
69 | self.text_input.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
70 | self.text_scroll.config(command=self.text_input.yview)
71 |
72 | def update_text_font(self, event=None):
73 | self.update_text_size()
74 |
75 | def update_text_size(self):
76 | new_font = (self.text_font.get(), self.text_size.get())
77 | self.text_input.configure(font=new_font)
78 |
79 | def calculate_and_display_tokens(self):
80 | # 獲取文本框內的文本
81 | text = self.text_input.get("1.0", tk.END)
82 | # 計算tokens
83 | tokens = calculate_tokens(text)
84 | # 更新標籤顯示tokens數量
85 | self.tokens_label.configure(text=f"Tokens: {tokens}")
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | if __name__ == "__main__":
97 | app = TokenCalculatorApp()
98 | app.mainloop()
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/My_Tools/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/My_Tools/__init__.py
--------------------------------------------------------------------------------
/My_Tools/check_gpu.py:
--------------------------------------------------------------------------------
1 | import torch
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | def torch_check_is_gpu_available():
13 | if torch.cuda.is_available():
14 | print(f"GPU is available: {torch.cuda.get_device_name(0)}")
15 |
16 | else:
17 | print("GPU is not available")
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | if __name__ == '__main__':
29 | torch_check_is_gpu_available()
30 | input("Press any key to continue")
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/My_Tools/check_gpu_torch.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd..
3 | GUI_control_panel\runtime\python.exe My_Tools\check_gpu.py
--------------------------------------------------------------------------------
/OBS_websocket/OBS_websocket.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 | import time
5 |
6 | import obswebsocket as obsWS
7 |
8 | import AIVT_Config
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | obsws = obsWS.obsws(AIVT_Config.OBS_WebSockets_ip, AIVT_Config.OBS_WebSockets_port, AIVT_Config.OBS_WebSockets_password)
20 |
21 | OBS_Connected = False
22 | OBS_Requesting = False
23 | OBS_subtitles_formatter_versions = ["v2", "v3"]
24 |
25 | OBS_subtitles_parameters = {
26 | "subtitles_formatter": True,
27 | "subtitles_formatter_version": "v3",
28 | }
29 |
30 | OBS_chat_now_sub_parameters = {
31 | "sub_name": "AIVT_CSub",
32 | "show": True,
33 | "clear": False,
34 | "sub_time": 3.0,
35 | "sub_max_length": 20,
36 | "sub_english_char_length": 0.50,
37 | "sub_base_line_count": 5,
38 | "sub_end_delay": 0.0,
39 |
40 | "show_sub_filter_names": ["Sub Appear"],
41 | "show_sub_filter_start_delay": 0.00,
42 | "show_sub_filter_end_delay": 0.00,
43 |
44 | "hide_sub_filter_names": ["Sub Disappear"],
45 | "hide_sub_filter_start_delay": 0.00,
46 | "hide_sub_filter_end_delay": 0.00,
47 | }
48 |
49 | OBS_ai_ans_sub_parameters = {
50 | "sub_name": "AIVT_Sub",
51 | "show": True,
52 | "clear": False,
53 | "remove_original_text_wrap": False,
54 | "sub_max_length": 20,
55 | "sub_english_char_length": 0.50,
56 | "sub_base_line_count": 5,
57 | "sub_end_delay": 0.0,
58 |
59 | "show_sub_filter_names": ["T Sub Appear"],
60 | "show_sub_filter_start_delay": 0.00,
61 | "show_sub_filter_end_delay": 0.00,
62 |
63 | "hide_sub_filter_names": ["T Sub Disappear"],
64 | "hide_sub_filter_start_delay": 0.00,
65 | "hide_sub_filter_end_delay": 0.00,
66 | }
67 |
68 |
69 |
70 |
71 |
72 | def OBSws_connect():
73 | global obsws, OBS_Connected
74 |
75 | try:
76 | obsws.connect()
77 | OBS_Connected = True
78 | print("!!! OBS WebSockets Connect Success !!!")
79 |
80 | except Exception as e:
81 | OBS_Connected = False
82 | print("!!! OBS WebSockets Connect Fail !!!\n{e}\n")
83 |
84 |
85 | def OBSws_disconnect():
86 | global obsws, OBS_Connected, OBS_Requesting
87 |
88 | while OBS_Requesting:
89 | time.sleep(0.1)
90 |
91 | try:
92 | obsws.disconnect()
93 | OBS_Connected = False
94 | print("!!! OBS WebSockets Disconnect !!!")
95 |
96 | except Exception as e:
97 | OBS_Connected = True
98 | print(f"!!! OBS WebSockets Disconnect Fail !!!\n{e}\n")
99 |
100 |
101 |
102 |
103 |
104 | def Set_Source_Text(text_name, text, start_delay=0, end_delay=0):
105 | global obsws, OBS_Connected, OBS_Requesting
106 |
107 | if OBS_Connected:
108 | OBS_Requesting = True
109 | time.sleep(start_delay)
110 |
111 | obsws.call(obsWS.requests.SetInputSettings(inputName=text_name, inputSettings={"text": text}))
112 |
113 | time.sleep(end_delay)
114 | OBS_Requesting = False
115 |
116 |
117 |
118 | def Set_Source_Filter_Enabled(source_name, filter_names, filter_enabled, start_delay=0, end_delay=0):
119 | global obsws, OBS_Connected, OBS_Requesting
120 |
121 | if OBS_Connected:
122 | OBS_Requesting = True
123 |
124 | filter_names_list = []
125 | if isinstance(filter_names, str):
126 | filter_names_list.append(filter_names)
127 |
128 | elif isinstance(filter_names, list):
129 | filter_names_list.extend(filter_names)
130 |
131 |
132 | time.sleep(start_delay)
133 |
134 | for filter_name in filter_names_list:
135 | obsws.call(obsWS.requests.SetSourceFilterEnabled(sourceName=source_name, filterName=filter_name, filterEnabled=filter_enabled))
136 |
137 | time.sleep(end_delay)
138 | OBS_Requesting = False
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/OBS_websocket/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/OBS_websocket/__init__.py
--------------------------------------------------------------------------------
/OpenAI/gpt/OpenAI_GPT_API.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4 | import threading
5 | import time
6 | import queue
7 |
8 | import openai
9 |
10 | import AIVT_Config
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | gpt_models_max_input_tokens = {
22 | "gpt-4o":128000,
23 | "gpt-4o-2024-11-20":128000,
24 | "gpt-4o-2024-08-06":128000,
25 | "gpt-4o-2024-05-13":128000,
26 | "gpt-4o-mini":128000,
27 | "gpt-4o-mini-2024-07-18":128000,
28 | "gpt-4.5-preview-2025-02-27":128000,
29 | }
30 |
31 | gpt_models_max_output_tokens = {
32 | "gpt-4o":16384,
33 | "gpt-4o-2024-11-20":16384,
34 | "gpt-4o-2024-08-06":16384,
35 | "gpt-4o-2024-05-13":4096,
36 | "gpt-4o-mini":16385,
37 | "gpt-4o-mini-2024-07-18":16385,
38 | "gpt-4.5-preview-2025-02-27":16385,
39 | }
40 |
41 | gpt_parameters = {
42 | "model": "gpt-4o-mini",
43 | "max_input_tokens" : 4096,
44 | "max_output_tokens" : 256,
45 | "temperature" : 1.00,
46 | "timeout" : 15,
47 | "retry" : 3,
48 | }
49 |
50 |
51 |
52 |
53 |
54 | def run_with_timeout_OpenAI_GPT_API(
55 | conversation,
56 | chatQ,
57 | model_name="gpt-4o-mini",
58 | max_output_tokens="256",
59 | temperature=1.00,
60 | timeout=15,
61 | retry=3,
62 | command=None,
63 | ):
64 |
65 | ans = queue.Queue()
66 | model = queue.Queue()
67 | prompt_tokens = queue.Queue()
68 | completion_tokens = queue.Queue()
69 | total_tokens = queue.Queue()
70 |
71 | start_time = time.time()
72 |
73 | OGAt = threading.Thread(
74 | target=OpenAI_GPT_API_thread,
75 | args=(
76 | conversation,
77 | ans,
78 | model,
79 | prompt_tokens,
80 | completion_tokens,
81 | total_tokens,
82 | ),
83 | kwargs={
84 | "model_name":model_name,
85 | "max_output_tokens":max_output_tokens,
86 | "temperature":temperature,
87 | "retry":retry,
88 | },
89 | )
90 |
91 | OGAt.start()
92 | OGAt.join(timeout)
93 |
94 | if OGAt.is_alive():
95 | return None
96 |
97 | else:
98 | llm_result = ans.get()
99 | if command != "no_print":
100 | end_time = time.time()
101 | print("\nOpenAI_GPT_Answer ----------\n")
102 | print(f"Model: {model.get()}")
103 | print(f"Duration: {end_time - start_time:.2f}s")
104 | print(f"Prompt tokens: {prompt_tokens.get()}")
105 | print(f"Completion tokens: {completion_tokens.get()}")
106 | print(f"Total tokens: {total_tokens.get()}\n")
107 | print(f"{chatQ}\n")
108 | print(f"GPT Answer : {llm_result}")
109 | print("\n----------\n")
110 |
111 | cleaned_llm_result = "\n".join(line.strip() for line in llm_result.splitlines() if line.strip())
112 | return cleaned_llm_result
113 |
114 |
115 | def OpenAI_GPT_API_thread(
116 | conversation,
117 | ans,
118 | model,
119 | prompt_tokens,
120 | completion_tokens,
121 | total_tokens,
122 | model_name = "gpt-4o-mini",
123 | max_output_tokens = 256,
124 | temperature = 1.00,
125 | retry = 3,
126 | ):
127 |
128 | openai.api_key = AIVT_Config.openai_api_key
129 |
130 | reT = 0
131 | while reT < retry:
132 | reT += 1
133 |
134 | try:
135 | response = openai.chat.completions.create(
136 | model = model_name,
137 | messages = conversation,
138 | max_tokens = max_output_tokens,
139 | temperature = temperature,
140 | stream=False,
141 | )
142 |
143 | model.put(response.model)
144 | prompt_tokens.put(response.usage.prompt_tokens)
145 | completion_tokens.put(response.usage.completion_tokens)
146 | total_tokens.put(response.usage.total_tokens)
147 | message = response.choices[0].message.content
148 |
149 | ans.put(message)
150 | return
151 |
152 | except Exception as e:
153 | if reT < retry:
154 | print(f"!!! OpenAI_GPT_API retry {reT} time !!!\n{e}\n")
155 | continue
156 |
157 | else:
158 | print(f"!!! OpenAI_GPT_API retry {reT} time !!!\n{e}\n")
159 | ans.put("")
160 | return
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/OpenAI/gpt/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/OpenAI/gpt/__init__.py
--------------------------------------------------------------------------------
/OpenAI/whisper/OpenAI_Whisper.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4 | import threading
5 | import queue
6 | import time
7 | import glob
8 | import gc
9 |
10 | import whisper
11 | import torch
12 |
13 | from My_Tools.AIVT_print import aprint
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Whisper_LANGUAGES = {
25 | "en": "english",
26 | "zh": "chinese",
27 | "ja": "japanese",
28 | "ko": "korean",
29 | "de": "german",
30 | "es": "spanish",
31 | "ru": "russian",
32 | "fr": "french",
33 | "pt": "portuguese",
34 | "tr": "turkish",
35 | "pl": "polish",
36 | "ca": "catalan",
37 | "nl": "dutch",
38 | "ar": "arabic",
39 | "sv": "swedish",
40 | "it": "italian",
41 | "id": "indonesian",
42 | "hi": "hindi",
43 | "fi": "finnish",
44 | "vi": "vietnamese",
45 | "he": "hebrew",
46 | "uk": "ukrainian",
47 | "el": "greek",
48 | "ms": "malay",
49 | "cs": "czech",
50 | "ro": "romanian",
51 | "da": "danish",
52 | "hu": "hungarian",
53 | "ta": "tamil",
54 | "no": "norwegian",
55 | "th": "thai",
56 | "ur": "urdu",
57 | "hr": "croatian",
58 | "bg": "bulgarian",
59 | "lt": "lithuanian",
60 | "la": "latin",
61 | "mi": "maori",
62 | "ml": "malayalam",
63 | "cy": "welsh",
64 | "sk": "slovak",
65 | "te": "telugu",
66 | "fa": "persian",
67 | "lv": "latvian",
68 | "bn": "bengali",
69 | "sr": "serbian",
70 | "az": "azerbaijani",
71 | "sl": "slovenian",
72 | "kn": "kannada",
73 | "et": "estonian",
74 | "mk": "macedonian",
75 | "br": "breton",
76 | "eu": "basque",
77 | "is": "icelandic",
78 | "hy": "armenian",
79 | "ne": "nepali",
80 | "mn": "mongolian",
81 | "bs": "bosnian",
82 | "kk": "kazakh",
83 | "sq": "albanian",
84 | "sw": "swahili",
85 | "gl": "galician",
86 | "mr": "marathi",
87 | "pa": "punjabi",
88 | "si": "sinhala",
89 | "km": "khmer",
90 | "sn": "shona",
91 | "yo": "yoruba",
92 | "so": "somali",
93 | "af": "afrikaans",
94 | "oc": "occitan",
95 | "ka": "georgian",
96 | "be": "belarusian",
97 | "tg": "tajik",
98 | "sd": "sindhi",
99 | "gu": "gujarati",
100 | "am": "amharic",
101 | "yi": "yiddish",
102 | "lo": "lao",
103 | "uz": "uzbek",
104 | "fo": "faroese",
105 | "ht": "haitian creole",
106 | "ps": "pashto",
107 | "tk": "turkmen",
108 | "nn": "nynorsk",
109 | "mt": "maltese",
110 | "sa": "sanskrit",
111 | "lb": "luxembourgish",
112 | "my": "myanmar",
113 | "bo": "tibetan",
114 | "tl": "tagalog",
115 | "mg": "malagasy",
116 | "as": "assamese",
117 | "tt": "tatar",
118 | "haw": "hawaiian",
119 | "ln": "lingala",
120 | "ha": "hausa",
121 | "ba": "bashkir",
122 | "jw": "javanese",
123 | "su": "sundanese",
124 | "yue": "cantonese",
125 | }
126 |
127 | whisper_status = {
128 | "loaded_model": "",
129 | "gui_selected_model": "base",
130 | "model_loding": False,
131 | }
132 |
133 | whisper_parameters = {
134 | "user_mic_language": "zh",
135 | "prompt": "",
136 | "max_tokens": 192,
137 | "temperature": 0.2,
138 | "timeout": 10,
139 | }
140 |
141 | model = None
142 |
143 |
144 |
145 |
146 |
147 | def get_available_model_names_list():
148 | pattern = os.path.join("OpenAI/whisper/models/", "*.pt")
149 | pt_files = glob.glob(pattern)
150 | model_names_list = [os.path.splitext(os.path.basename(file))[0] for file in pt_files]
151 | return model_names_list
152 |
153 |
154 |
155 | def load_model(model_name="base"):
156 | global model, whisper_status
157 |
158 | whisper_status["model_loding"] = True
159 | aprint(f"Whisper model loading: {model_name}")
160 |
161 | try:
162 | if whisper_status["loaded_model"]:
163 | del model
164 | gc.collect()
165 | if torch.cuda.is_available():
166 | torch.cuda.empty_cache()
167 |
168 | start_time = time.time()
169 |
170 | model = whisper.load_model(name=f"OpenAI/whisper/models/{model_name}.pt", )
171 |
172 | end_time = time.time()
173 |
174 | aprint(f"!!! Whisper Loaded Model Success: {model_name} !!!\nLoading time: {end_time - start_time:.2f}s\n")
175 | whisper_status["loaded_model"] = model_name
176 |
177 | except Exception as e:
178 | aprint(f"!!! Whisper Loaded Model Fail: {model_name} !!!\n{e}\n")
179 | whisper_status["loaded_model"] = ""
180 |
181 | whisper_status["model_loding"] = False
182 |
183 |
184 | def unload_model():
185 | global model, whisper_status
186 |
187 | if whisper_status["loaded_model"]:
188 | try:
189 | del model
190 | gc.collect()
191 | if torch.cuda.is_available():
192 | torch.cuda.empty_cache()
193 |
194 | aprint(f"!!! Model Unloaded Success: {whisper_status['loaded_model']} !!!\n")
195 | whisper_status["loaded_model"] = ""
196 | model = None
197 |
198 | except Exception as e:
199 | aprint(f"!!! Model Unloaded fail: {whisper_status['loaded_model']} !!!\n{e}\n")
200 |
201 |
202 |
203 |
204 |
205 | def run_with_timeout_OpenAI_Whisper(
206 | audio_path = "audio/input_user_mic.wav",
207 | audio_language = "zh",
208 | prompt = "",
209 | max_tokens = 192,
210 | temperature = 0.2,
211 | timeout=10,
212 | ):
213 | global whisper_status
214 |
215 | while whisper_status["model_loding"]:
216 | time.sleep(0.1)
217 |
218 | if whisper_status["loaded_model"] == "":
219 | load_model(model_name=whisper_status["gui_selected_model"])
220 |
221 | if whisper_status["loaded_model"] == "":
222 | print("\n!!! OpenAI Whisper Transcribe Fail !!!\n")
223 | return ""
224 |
225 | model_name = whisper_status["loaded_model"]
226 |
227 | transcribe_ans = queue.Queue()
228 |
229 | start_time = time.time()
230 |
231 | OWt = threading.Thread(
232 | target = OpenAI_Whisper_thread,
233 | args = (transcribe_ans, ),
234 | kwargs = {
235 | "audio_path": audio_path,
236 | "audio_language": audio_language,
237 | "prompt": prompt,
238 | "max_tokens": max_tokens,
239 | "temperature": temperature,
240 | },
241 | )
242 |
243 | OWt.start()
244 | OWt.join(timeout)
245 |
246 | if OWt.is_alive():
247 | print("\n!!! OpenAI Whisper time out !!!\n")
248 | return ""
249 |
250 | else:
251 | end_time = time.time()
252 | whisper_result = transcribe_ans.get()
253 | print("\nOpenAI Whisper Local ----------\n")
254 | print(f"Model: {model_name}")
255 | print(f"Duration: {end_time - start_time:.2f}s\n")
256 | print(f"Transcribe: {whisper_result}")
257 | print("\n----------\n")
258 | return whisper_result
259 |
260 |
261 | def OpenAI_Whisper_thread(
262 | ans,
263 | audio_path="audio/input_user_mic.wav",
264 | audio_language="zh",
265 | prompt="",
266 | max_tokens=192,
267 | temperature=0.2,
268 | ):
269 | global model
270 |
271 | try:
272 | result = model.transcribe(
273 | audio = audio_path,
274 | language = audio_language,
275 | initial_prompt = prompt,
276 | temperature=temperature,
277 | sample_len=max_tokens,
278 | )
279 |
280 | except Exception as e:
281 | aprint(f"!!! Fail Transcribing !!!\n{e}\n")
282 | ans.put("")
283 | return
284 |
285 | ans.put(result["text"])
286 | return
287 |
288 |
289 |
290 |
291 |
--------------------------------------------------------------------------------
/OpenAI/whisper/OpenAI_Whisper_API.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
4 | import threading
5 | import time
6 | import queue
7 |
8 | import openai
9 |
10 | import AIVT_Config
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Whisper_LANGUAGES = {
22 | "en": "english",
23 | "zh": "chinese",
24 | "ja": "japanese",
25 | "ko": "korean",
26 | "de": "german",
27 | "es": "spanish",
28 | "ru": "russian",
29 | "fr": "french",
30 | "pt": "portuguese",
31 | "tr": "turkish",
32 | "pl": "polish",
33 | "ca": "catalan",
34 | "nl": "dutch",
35 | "ar": "arabic",
36 | "sv": "swedish",
37 | "it": "italian",
38 | "id": "indonesian",
39 | "hi": "hindi",
40 | "fi": "finnish",
41 | "vi": "vietnamese",
42 | "he": "hebrew",
43 | "uk": "ukrainian",
44 | "el": "greek",
45 | "ms": "malay",
46 | "cs": "czech",
47 | "ro": "romanian",
48 | "da": "danish",
49 | "hu": "hungarian",
50 | "ta": "tamil",
51 | "no": "norwegian",
52 | "th": "thai",
53 | "ur": "urdu",
54 | "hr": "croatian",
55 | "bg": "bulgarian",
56 | "lt": "lithuanian",
57 | "la": "latin",
58 | "mi": "maori",
59 | "ml": "malayalam",
60 | "cy": "welsh",
61 | "sk": "slovak",
62 | "te": "telugu",
63 | "fa": "persian",
64 | "lv": "latvian",
65 | "bn": "bengali",
66 | "sr": "serbian",
67 | "az": "azerbaijani",
68 | "sl": "slovenian",
69 | "kn": "kannada",
70 | "et": "estonian",
71 | "mk": "macedonian",
72 | "br": "breton",
73 | "eu": "basque",
74 | "is": "icelandic",
75 | "hy": "armenian",
76 | "ne": "nepali",
77 | "mn": "mongolian",
78 | "bs": "bosnian",
79 | "kk": "kazakh",
80 | "sq": "albanian",
81 | "sw": "swahili",
82 | "gl": "galician",
83 | "mr": "marathi",
84 | "pa": "punjabi",
85 | "si": "sinhala",
86 | "km": "khmer",
87 | "sn": "shona",
88 | "yo": "yoruba",
89 | "so": "somali",
90 | "af": "afrikaans",
91 | "oc": "occitan",
92 | "ka": "georgian",
93 | "be": "belarusian",
94 | "tg": "tajik",
95 | "sd": "sindhi",
96 | "gu": "gujarati",
97 | "am": "amharic",
98 | "yi": "yiddish",
99 | "lo": "lao",
100 | "uz": "uzbek",
101 | "fo": "faroese",
102 | "ht": "haitian creole",
103 | "ps": "pashto",
104 | "tk": "turkmen",
105 | "nn": "nynorsk",
106 | "mt": "maltese",
107 | "sa": "sanskrit",
108 | "lb": "luxembourgish",
109 | "my": "myanmar",
110 | "bo": "tibetan",
111 | "tl": "tagalog",
112 | "mg": "malagasy",
113 | "as": "assamese",
114 | "tt": "tatar",
115 | "haw": "hawaiian",
116 | "ln": "lingala",
117 | "ha": "hausa",
118 | "ba": "bashkir",
119 | "jw": "javanese",
120 | "su": "sundanese",
121 | "yue": "cantonese",
122 | }
123 |
124 | whisper_parameters = {
125 | "model":"whisper-1",
126 | "user_mic_language":"zh",
127 | "prompt":"",
128 | "temperature":0.2,
129 | "timeout":10,
130 | }
131 |
132 |
133 |
134 |
135 |
136 | def run_with_timeout_OpenAI_Whisper_API(
137 | model = "whisper-1",
138 | audio_path = "audio/input_user_mic.wav",
139 | audio_language = "zh",
140 | prompt = "",
141 | temperature = 0.2,
142 | timeout=10,
143 | ):
144 |
145 | transcribe_ans = queue.Queue()
146 |
147 | start_time = time.time()
148 |
149 | OWAt = threading.Thread(
150 | target = OpenAI_Whisper_API_thread,
151 | args = (transcribe_ans, ),
152 | kwargs = {
153 | "model": model,
154 | "audio_path": audio_path,
155 | "audio_language": audio_language,
156 | "prompt": prompt,
157 | "temperature": temperature,
158 | },
159 | )
160 |
161 | OWAt.start()
162 | OWAt.join(timeout)
163 |
164 | if OWAt.is_alive():
165 | print("\n!!! OpenAI Whisper API time out !!!\n")
166 | return ""
167 |
168 | else:
169 | end_time = time.time()
170 | whisper_result = transcribe_ans.get()
171 | print("\nOpenAI Whisper API ----------\n")
172 | print(f"Duration: {end_time - start_time:.2f}s\n")
173 | print(f"Transcribe: {whisper_result}")
174 | print("\n----------\n")
175 | return whisper_result
176 |
177 |
178 | def OpenAI_Whisper_API_thread(
179 | ans,
180 | model = "whisper-1",
181 | audio_path = "audio/input_user_mic.wav",
182 | audio_language = "zh",
183 | prompt = "",
184 | temperature = 0.2,
185 | ):
186 |
187 | openai.api_key = AIVT_Config.openai_api_key
188 |
189 | try:
190 | audio_file = open(audio_path, "rb")
191 | transcript = openai.audio.transcriptions.create(
192 | model=model,
193 | file=audio_file,
194 | language=audio_language,
195 | prompt=prompt,
196 | temperature=temperature,
197 | )
198 |
199 | ans.put(transcript.text)
200 | return
201 |
202 | except Exception as e:
203 | print(f"Error transcribing audio\n{e}\n")
204 | ans.put("")
205 | return
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/OpenAI/whisper/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/OpenAI/whisper/__init__.py
--------------------------------------------------------------------------------
/OpenAI/whisper/models/tiny.pt:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:65147644a518d12f04e32d6f3b26facc3f8dd46e5390956a9424a650c0ce22b9
3 | size 75572083
4 |
--------------------------------------------------------------------------------
/Play_Audio.py:
--------------------------------------------------------------------------------
1 | import os
2 | import io
3 | import wave
4 | current_dir = os.path.dirname(os.path.abspath(__file__))
5 | bin_dir = os.path.join(current_dir, "GUI_control_panel")
6 | bin_dir = os.path.abspath(bin_dir)
7 | os.environ["PATH"] += os.pathsep + bin_dir
8 |
9 | import pyaudio
10 | from pydub import AudioSegment
11 | ffmpeg_path = os.path.join(bin_dir, "ffmpeg.exe")
12 | AudioSegment.converter = ffmpeg_path
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | play_audio_parameters = {
24 | "ai_voice_output_device_name": "",
25 | "chunk": 1024,
26 | }
27 |
28 |
29 |
30 | def PlayAudio(audio_path, output_device_name, command=None):
31 | global play_audio_parameters
32 |
33 | output_device_index = Get_available_output_devices_ID(output_device_name)
34 |
35 | _, file_extension = os.path.splitext(audio_path)
36 | if file_extension.lower() == '.wav':
37 | audio_data = open(audio_path, 'rb')
38 | else:
39 | audio = AudioSegment.from_file(audio_path)
40 | audio_data = io.BytesIO()
41 | audio.export(audio_data, format="wav")
42 | audio_data.seek(0)
43 |
44 | wf = wave.open(audio_data, 'rb')
45 | p = pyaudio.PyAudio()
46 |
47 | CHUNK = play_audio_parameters["chunk"]
48 | stream = p.open(
49 | output=True,
50 | output_device_index=output_device_index,
51 | channels=wf.getnchannels(),
52 | format=p.get_format_from_width(wf.getsampwidth()),
53 | rate=wf.getframerate(),
54 | )
55 |
56 | data = wf.readframes(CHUNK)
57 |
58 | while data:
59 | stream.write(data)
60 | data = wf.readframes(CHUNK)
61 |
62 | stream.stop_stream()
63 | stream.close()
64 |
65 | p.terminate()
66 |
67 |
68 |
69 |
70 |
71 | def Available_output_devices():
72 | p = pyaudio.PyAudio()
73 | info = p.get_host_api_info_by_index(0)
74 | numdevices = info.get('deviceCount')
75 |
76 | for i in range(0, numdevices):
77 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')) > 0:
78 | print(f"Output Device id {i} - {p.get_device_info_by_host_api_device_index(0, i).get('name')}")
79 |
80 | p.terminate()
81 |
82 |
83 | def Get_available_output_devices_List():
84 | p = pyaudio.PyAudio()
85 | info = p.get_host_api_info_by_index(0)
86 | numdevices = info.get('deviceCount')
87 | output_devices_list = []
88 | for i in range(0, numdevices):
89 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')) > 0:
90 | output_devices_list.append(p.get_device_info_by_host_api_device_index(0, i).get('name'))
91 |
92 | p.terminate()
93 | return output_devices_list
94 |
95 |
96 | def Get_available_output_devices_ID(devices_name):
97 | p = pyaudio.PyAudio()
98 | info = p.get_host_api_info_by_index(0)
99 | numdevices = info.get('deviceCount')
100 |
101 | # find output device id by device name
102 | for i in range(0, numdevices):
103 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')) > 0:
104 | if devices_name == p.get_device_info_by_host_api_device_index(0, i).get('name'):
105 | p.terminate()
106 | return i
107 |
108 | # if the name is not in output device list
109 | #return default system audio output device
110 | for i in range(0, numdevices):
111 | if (p.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')) > 0:
112 | p.terminate()
113 | return i
114 |
115 | p.terminate()
116 | return None
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | if __name__ == "__main__":
128 | Available_output_devices()
129 |
130 |
131 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AI-VTuber-System
2 | A graphical system program that allows you to quickly create your own AI VTuber for free.
3 | 
4 |
5 | ## Tutorial Video
6 | https://www.youtube.com/watch?v=Hwss_p2Iroc
7 |
8 | ## User Manual
9 | https://docs.google.com/document/d/16DU-DJKMaC-15K6iShLd9ioXc8VqjTLqgMPswsjPjF0/edit?usp=sharing
10 |
11 | ## Installation Guide
12 | Python >= 3.8, install the main dependencies:
13 | ```
14 | pip3 install -r requirements.txt
15 | ```
16 | For the specific PyTorch packages which require a special handling, use the following command:
17 | ```
18 | pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
19 | ```
20 | ## Nvidia
21 | Latest GPU Driver
22 | https://www.nvidia.com.tw/Download/index.aspx?lang=tw
23 |
24 | CUDA Toolkit 12.1.1
25 | https://developer.nvidia.com/cuda-12-1-1-download-archive
26 |
27 | cuDNN
28 | https://developer.nvidia.com/cudnn-downloads
29 |
30 | ## Whisper
31 | Excerpt from https://github.com/openai/whisper
32 |
33 | There are six model sizes, four with English-only versions, offering speed and accuracy tradeoffs.
34 | Below are the names of the available models and their approximate memory requirements and inference speed relative to the large model.
35 | The relative speeds below are measured by transcribing English speech on a A100, and the real-world speed may vary significantly depending on many factors including the language, the speaking speed, and the available hardware.
36 |
37 | | Size | Parameters | English-only model | Multilingual model | Required VRAM | Relative speed |
38 | |:------:|:----------:|:------------------:|:------------------:|:-------------:|:--------------:|
39 | | tiny | 39 M | `tiny.en` | `tiny` | ~1 GB | ~10x |
40 | | base | 74 M | `base.en` | `base` | ~1 GB | ~7x |
41 | | small | 244 M | `small.en` | `small` | ~2 GB | ~4x |
42 | | medium | 769 M | `medium.en` | `medium` | ~5 GB | ~2x |
43 | | large | 1550 M | N/A | `large` | ~10 GB | 1x |
44 | | turbo | 809 M | N/A | `turbo` | ~6 GB | ~8x |
45 |
46 | The `.en` models for English-only applications tend to perform better, especially for the `tiny.en` and `base.en` models. We observed that the difference becomes less significant for the `small.en` and `medium.en` models.
47 | Additionally, the `turbo` model is an optimized version of `large-v3` that offers faster transcription speed with a minimal degradation in accuracy.
48 |
49 | Whisper's performance varies widely depending on the language. The figure below shows a performance breakdown of `large-v3` and `large-v2` models by language, using WERs (word error rates) or CER (character error rates, shown in *Italic*) evaluated on the Common Voice 15 and Fleurs datasets. Additional WER/CER metrics corresponding to the other models and datasets can be found in Appendix D.1, D.2, and D.4 of [the paper](https://arxiv.org/abs/2212.04356), as well as the BLEU (Bilingual Evaluation Understudy) scores for translation in Appendix D.3.
50 |
51 | 
52 |
53 |
54 | ## Whisper Model Download Links
55 | tiny.en: https://openaipublic.azureedge.net/main/whisper/models/d3dd57d32accea0b295c96e26691aa14d8822fac7d9d27d5dc00b4ca2826dd03/tiny.en.pt
56 |
57 | tiny: https://openaipublic.azureedge.net/main/whisper/models/65147644a518d12f04e32d6f3b26facc3f8dd46e5390956a9424a650c0ce22b9/tiny.pt
58 |
59 | base.en: https://openaipublic.azureedge.net/main/whisper/models/25a8566e1d0c1e2231d1c762132cd20e0f96a85d16145c3a00adf5d1ac670ead/base.en.pt
60 |
61 | base: https://openaipublic.azureedge.net/main/whisper/models/ed3a0b6b1c0edf879ad9b11b1af5a0e6ab5db9205f891f668f8b0e6c6326e34e/base.pt
62 |
63 | small.en: https://openaipublic.azureedge.net/main/whisper/models/f953ad0fd29cacd07d5a9eda5624af0f6bcf2258be67c92b79389873d91e0872/small.en.pt
64 |
65 | small: https://openaipublic.azureedge.net/main/whisper/models/9ecf779972d90ba49c06d968637d720dd632c55bbf19d441fb42bf17a411e794/small.pt
66 |
67 | medium.en: https://openaipublic.azureedge.net/main/whisper/models/d7440d1dc186f76616474e0ff0b3b6b879abc9d1a4926b7adfa41db2d497ab4f/medium.en.pt
68 |
69 | medium: https://openaipublic.azureedge.net/main/whisper/models/345ae4da62f9b3d59415adc60127b97c714f32e89e936602e85993674d08dcb1/medium.pt
70 |
71 | large-v1: https://openaipublic.azureedge.net/main/whisper/models/e4b87e7e0bf463eb8e6956e646f1e277e901512310def2c24bf0e11bd3c28e9a/large-v1.pt
72 |
73 | large-v2: https://openaipublic.azureedge.net/main/whisper/models/81f7c96c852ee8fc832187b0132e569d6c3065a3252ed18e56effd0b6a73e524/large-v2.pt
74 |
75 | large-v3: https://openaipublic.azureedge.net/main/whisper/models/e5b1a55b89c1367dacf97e3e19bfd829a01529dbfdeefa8caeb59b3f1b81dadb/large-v3.pt
76 |
77 | turbo: https://openaipublic.azureedge.net/main/whisper/models/aff26ae408abcba5fbf8813c21e62b0941638c5f6eebfb145be0c9839262a19a/large-v3-turbo.pt
78 |
79 | ## Star History
80 |
81 | [](https://www.star-history.com/#AIVTDevPKevin/AI-VTuber-System&Date)
82 |
--------------------------------------------------------------------------------
/Sentiment_Analysis/NLP_API.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 | import string
5 | import time
6 | from collections import Counter
7 |
8 | import Google.gemini.GoogleAI_Gemini_API as gemini_api
9 | import OpenAI.gpt.OpenAI_GPT_API as gpt_api
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | gemini_model_names_list = [name for name in gemini_api.gemini_models_max_input_tokens.keys()]
21 | gpt_model_names_list = [name for name in gpt_api.gpt_models_max_input_tokens.keys()]
22 | model_names_list = gemini_model_names_list + gpt_model_names_list
23 |
24 |
25 |
26 |
27 |
28 | def Sentiment_Analysis_NLP(
29 | text,
30 | Emo_state_categories=["normal", "happy", "shy", "proud", "shock", "sad", "angry", "embarrass", "afraid", "confuse"],
31 | model="gemini-2.0-flash",
32 | timeout=5,
33 | ):
34 | global gemini_model_names_list, gpt_model_names_list
35 |
36 | SA_system_prompt = f"You will be responsible for analysing the text sent to you and sending back the corresponding sentiment categories in English. According to the following categories of sentiment that I have defined: {Emo_state_categories}, just send back the one most likely sentiment, and only return the name of the sentiment. Don't send me back the text that was sent to you."
37 | conversation = [
38 | {"role": "system", "content": SA_system_prompt},
39 | {"role": "user", "content": "(・_・;) No, PK, why did you suddenly say this? Are you trying to steal my little fish? (@_@;)\(◎o◎)/!"},
40 | {"role": "assistant", "content": "confuse"},
41 | {"role": "user", "content": "PK、君は悪いよ(/‵Д′)/~ ╧╧ C0はパクチーが一番嫌い(╬゚д゚) パクチーもセロリもこの世に出てはいけない(╯°□°)╯︵ ┻━┻"},
42 | {"role": "assistant", "content": "angry"},
43 | {"role": "user", "content": "(゜o゜) 欸欸欸!PK你在幹嘛?學貓叫嗎? 笑死!這個PK就是遜啦!哈哈! (≧∇≦)/ "}, # 🥲
44 | {"role": "assistant", "content": "happy"},
45 | {"role": "user", "content": text},
46 | ]
47 |
48 | start_time = time.time()
49 |
50 | if model in gemini_model_names_list:
51 | llm_result = gemini_api.run_with_timeout_GoogleAI_Gemini_API(
52 | conversation,
53 | "",
54 | model_name=model,
55 | max_output_tokens=10,
56 | temperature=0.2,
57 | timeout=timeout,
58 | retry=1,
59 | command="no_print",
60 | )
61 |
62 | elif model in gpt_model_names_list:
63 | llm_result = gpt_api.run_with_timeout_OpenAI_GPT_API(
64 | conversation,
65 | "",
66 | model_name=model,
67 | max_output_tokens=10,
68 | temperature=0.2,
69 | timeout=timeout,
70 | retry=1,
71 | command="no_print",
72 | )
73 |
74 | try:
75 | mcsw = most_common_specific_word(llm_result, Emo_state_categories)
76 | except Exception as e:
77 | print(f"\n{e}\n")
78 | mcsw = "normal"
79 |
80 | end_time = time.time()
81 | print("\nSentiment Analysis NLP ----------\n")
82 | print(f"Model: {model}")
83 | print(f"Duration: {end_time - start_time:.2f}s\n")
84 | print(f"Emotion State: {mcsw}")
85 | print("\n----------\n")
86 | return mcsw
87 |
88 |
89 |
90 | def most_common_specific_word(text, Emo_state_categories):
91 | punctuation = string.punctuation + ":"
92 | translator = str.maketrans(punctuation, " " * len(punctuation))
93 |
94 | text = text.lower()
95 | text = text.translate(translator)
96 | words = text.split()
97 | word_counts = Counter(words)
98 |
99 | Emo_state_categories = [word.lower() for word in Emo_state_categories]
100 | result = {word: word_counts[word] for word in Emo_state_categories}
101 | most_common_word = max(result, key=result.get)
102 | return most_common_word
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | if __name__ == "__main__":
114 |
115 | emo_state = Sentiment_Analysis_NLP(
116 | "Never gonna make you cry never gonna say goodbye"
117 | )
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/Sentiment_Analysis/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/Sentiment_Analysis/__init__.py
--------------------------------------------------------------------------------
/TextToSpeech/OpenAITTS.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 |
5 | import openai
6 |
7 | import AIVT_Config
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | OpenAITTS_Voice_list = ["alloy", "echo", "fable", "onyx", "nova", "shimmer", ]
19 | OpenAITTS_Model_list = ["tts-1", "tts-1-hd", ]
20 |
21 | openaitts_parameters = {
22 | "output_path":"Audio/output_openaitts_01.wav",
23 | "model":"tts-1",
24 | "voice":"alloy",
25 | "speed":1.00,
26 | "format":"wav",
27 | "timeout":10.0,
28 | }
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | def openaitts(
40 | text="",
41 | output_path="Audio/output_openaitts_01.wav",
42 | model="tts-1",
43 | voice="alloy",
44 | speed=1.00,
45 | format="wav",
46 | timeout=10.0,
47 | ):
48 |
49 | openai.api_key = AIVT_Config.openai_api_key
50 |
51 | response = openai.audio.speech.create(
52 | input=text,
53 | model=model,
54 | voice=voice,
55 | speed=speed,
56 | response_format=format,
57 | timeout=timeout,
58 | )
59 |
60 | try:
61 | response.write_to_file(output_path)
62 |
63 | except Exception as e:
64 | print(f"Error openai tts\n{e}\n")
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | if __name__ == "__main__":
76 |
77 | input_text = "Never gonna give you up never gonna let you down"
78 |
79 | openaitts(
80 | text=input_text,
81 | output_path="Audio/output_openaitts_01.wav",
82 | model="tts-1",
83 | voice="alloy",
84 | speed=1.00,
85 | format="wav",
86 | timeout=10.0,
87 | )
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/TextToSpeech/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/TextToSpeech/__init__.py
--------------------------------------------------------------------------------
/TextToSpeech/edgeTTS.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 | import asyncio
5 |
6 | import edge_tts
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | EdgeTTS_Voice_dict = {}
18 |
19 | edgetts_parameters = {
20 | "output_path":"Audio/output_edgetts_01.mp3",
21 | "voice":"zh-TW-HsiaoChenNeural",
22 | "pitch":"+0Hz",
23 | "rate":"+0%",
24 | "volume":"+0%",
25 | "timeout":10,
26 | }
27 |
28 |
29 |
30 |
31 |
32 | async def async_edgetts(text, output_path, voice, pitch, rate, volume, timeout) -> None:
33 | communicate = edge_tts.Communicate(text=text, voice=voice, pitch=pitch, rate=rate, volume=volume, receive_timeout=timeout)
34 | await communicate.save(output_path)
35 |
36 |
37 | def edgetts(text="",
38 | output_path="Audio/output_edgetts_01.mp3",
39 | voice="zh-TW-HsiaoChenNeural",
40 | pitch="+0Hz", rate="+0%", volume="+0%", timeout=10,
41 | ):
42 | asyncio.run(async_edgetts(text, output_path, voice, pitch, rate, volume, timeout))
43 |
44 |
45 |
46 |
47 |
48 | def create_voices_dict(file_path):
49 | voices_dict = {"Male": [], "Female": []}
50 |
51 | with open(file_path, 'r', encoding='utf-8') as file:
52 | for line in file:
53 | if "Name" in line:
54 | name = line.split(":")[1].strip()
55 |
56 | elif "Gender" in line:
57 | gender = line.split(":")[1].strip()
58 | voices_dict[gender].append(name)
59 |
60 | return {gender: names for gender, names in voices_dict.items() if names}
61 |
62 |
63 | def filter_voices_by_gender(voices_dict, gender):
64 | if gender == "All":
65 | return [name for names in voices_dict.values() for name in names]
66 | else:
67 | return voices_dict.get(gender, [])
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | if __name__ == "__main__":
79 |
80 | # Pitch -100Hz ~ +100Hz
81 | # Rate -100% ~ +100%
82 | # Volume -100% ~ +100%
83 |
84 |
85 | input_text = "Never gonna give you up never gonna let you down"
86 | edgetts(text=input_text,
87 | output_path="Audio/output_edgetts_01.mp3",
88 | voice="en-US-MichelleNeural",
89 | pitch="+30Hz",
90 | rate="-20%",
91 | volume="+0%",
92 | )
93 |
94 |
95 | '''
96 | input_text = "注意看,這個男人太狠了!"
97 | edgetts(text=input_text,
98 | output_path="Audio/output_edgetts_01.mp3",
99 | voice="zh-CN-YunxiNeural",
100 | pitch="+0Hz",
101 | rate="+0%",
102 | volume="+0%",
103 | )
104 | '''
105 |
106 | '''
107 | input_text = "廢話,車不改要怎麼騎?"
108 | edgetts(text=input_text,
109 | output_path="Audio/output_edgetts_01.mp3",
110 | voice="zh-TW-YunJheNeural",
111 | pitch="+0Hz",
112 | rate="+0%",
113 | volume="+0%",
114 | )
115 | '''
116 |
117 | '''
118 | input_text = "寶貝,你好大喔,做得好累"
119 | edgetts(text=input_text,
120 | output_path="Audio/output_edgetts_01.mp3",
121 | voice="zh-TW-HsiaoChenNeural",
122 | pitch="+15Hz",
123 | rate="-10%",
124 | volume="+0%",
125 | )
126 | '''
127 |
128 | '''
129 | input_text = "りしれ供さ小"
130 | edgetts(text=input_text,
131 | output_path="Audio/output_edgetts_01.mp3",
132 | voice="ja-JP-NanamiNeural",
133 | pitch="+15Hz",
134 | rate="-10%",
135 | volume="+0%",
136 | )
137 | '''
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/TextToSpeech/edgeTTS_speakers.txt:
--------------------------------------------------------------------------------
1 | Name: zh-TW-HsiaoChenNeural
2 | Gender: Female
3 |
4 | Name: zh-TW-HsiaoYuNeural
5 | Gender: Female
6 |
7 | Name: zh-TW-YunJheNeural
8 | Gender: Male
9 |
10 | Name: zh-CN-XiaoxiaoNeural
11 | Gender: Female
12 |
13 | Name: zh-CN-XiaoyiNeural
14 | Gender: Female
15 |
16 | Name: zh-CN-YunjianNeural
17 | Gender: Male
18 |
19 | Name: zh-CN-YunxiNeural
20 | Gender: Male
21 |
22 | Name: zh-CN-YunxiaNeural
23 | Gender: Male
24 |
25 | Name: zh-CN-YunyangNeural
26 | Gender: Male
27 |
28 | Name: zh-CN-liaoning-XiaobeiNeural
29 | Gender: Female
30 |
31 | Name: zh-CN-shaanxi-XiaoniNeural
32 | Gender: Female
33 |
34 | Name: zh-HK-HiuGaaiNeural
35 | Gender: Female
36 |
37 | Name: zh-HK-HiuMaanNeural
38 | Gender: Female
39 |
40 | Name: zh-HK-WanLungNeural
41 | Gender: Male
42 |
43 | Name: en-AU-NatashaNeural
44 | Gender: Female
45 |
46 | Name: en-AU-WilliamNeural
47 | Gender: Male
48 |
49 | Name: en-CA-ClaraNeural
50 | Gender: Female
51 |
52 | Name: en-CA-LiamNeural
53 | Gender: Male
54 |
55 | Name: en-GB-LibbyNeural
56 | Gender: Female
57 |
58 | Name: en-GB-MaisieNeural
59 | Gender: Female
60 |
61 | Name: en-GB-RyanNeural
62 | Gender: Male
63 |
64 | Name: en-GB-SoniaNeural
65 | Gender: Female
66 |
67 | Name: en-GB-ThomasNeural
68 | Gender: Male
69 |
70 | Name: en-HK-SamNeural
71 | Gender: Male
72 |
73 | Name: en-HK-YanNeural
74 | Gender: Female
75 |
76 | Name: en-IE-ConnorNeural
77 | Gender: Male
78 |
79 | Name: en-IE-EmilyNeural
80 | Gender: Female
81 |
82 | Name: en-IN-NeerjaExpressiveNeural
83 | Gender: Female
84 |
85 | Name: en-IN-NeerjaNeural
86 | Gender: Female
87 |
88 | Name: en-IN-PrabhatNeural
89 | Gender: Male
90 |
91 | Name: en-KE-AsiliaNeural
92 | Gender: Female
93 |
94 | Name: en-KE-ChilembaNeural
95 | Gender: Male
96 |
97 | Name: en-NG-AbeoNeural
98 | Gender: Male
99 |
100 | Name: en-NG-EzinneNeural
101 | Gender: Female
102 |
103 | Name: en-NZ-MitchellNeural
104 | Gender: Male
105 |
106 | Name: en-NZ-MollyNeural
107 | Gender: Female
108 |
109 | Name: en-PH-JamesNeural
110 | Gender: Male
111 |
112 | Name: en-PH-RosaNeural
113 | Gender: Female
114 |
115 | Name: en-SG-LunaNeural
116 | Gender: Female
117 |
118 | Name: en-SG-WayneNeural
119 | Gender: Male
120 |
121 | Name: en-TZ-ElimuNeural
122 | Gender: Male
123 |
124 | Name: en-TZ-ImaniNeural
125 | Gender: Female
126 |
127 | Name: en-US-AnaNeural
128 | Gender: Female
129 |
130 | Name: en-US-AriaNeural
131 | Gender: Female
132 |
133 | Name: en-US-ChristopherNeural
134 | Gender: Male
135 |
136 | Name: en-US-EricNeural
137 | Gender: Male
138 |
139 | Name: en-US-GuyNeural
140 | Gender: Male
141 |
142 | Name: en-US-JennyNeural
143 | Gender: Female
144 |
145 | Name: en-US-MichelleNeural
146 | Gender: Female
147 |
148 | Name: en-US-RogerNeural
149 | Gender: Male
150 |
151 | Name: en-US-SteffanNeural
152 | Gender: Male
153 |
154 | Name: en-ZA-LeahNeural
155 | Gender: Female
156 |
157 | Name: en-ZA-LukeNeural
158 | Gender: Male
159 |
160 | Name: ja-JP-KeitaNeural
161 | Gender: Male
162 |
163 | Name: ja-JP-NanamiNeural
164 | Gender: Female
165 |
166 | Name: af-ZA-AdriNeural
167 | Gender: Female
168 |
169 | Name: af-ZA-WillemNeural
170 | Gender: Male
171 |
172 | Name: am-ET-AmehaNeural
173 | Gender: Male
174 |
175 | Name: am-ET-MekdesNeural
176 | Gender: Female
177 |
178 | Name: ar-AE-FatimaNeural
179 | Gender: Female
180 |
181 | Name: ar-AE-HamdanNeural
182 | Gender: Male
183 |
184 | Name: ar-BH-AliNeural
185 | Gender: Male
186 |
187 | Name: ar-BH-LailaNeural
188 | Gender: Female
189 |
190 | Name: ar-DZ-AminaNeural
191 | Gender: Female
192 |
193 | Name: ar-DZ-IsmaelNeural
194 | Gender: Male
195 |
196 | Name: ar-EG-SalmaNeural
197 | Gender: Female
198 |
199 | Name: ar-EG-ShakirNeural
200 | Gender: Male
201 |
202 | Name: ar-IQ-BasselNeural
203 | Gender: Male
204 |
205 | Name: ar-IQ-RanaNeural
206 | Gender: Female
207 |
208 | Name: ar-JO-SanaNeural
209 | Gender: Female
210 |
211 | Name: ar-JO-TaimNeural
212 | Gender: Male
213 |
214 | Name: ar-KW-FahedNeural
215 | Gender: Male
216 |
217 | Name: ar-KW-NouraNeural
218 | Gender: Female
219 |
220 | Name: ar-LB-LaylaNeural
221 | Gender: Female
222 |
223 | Name: ar-LB-RamiNeural
224 | Gender: Male
225 |
226 | Name: ar-LY-ImanNeural
227 | Gender: Female
228 |
229 | Name: ar-LY-OmarNeural
230 | Gender: Male
231 |
232 | Name: ar-MA-JamalNeural
233 | Gender: Male
234 |
235 | Name: ar-MA-MounaNeural
236 | Gender: Female
237 |
238 | Name: ar-OM-AbdullahNeural
239 | Gender: Male
240 |
241 | Name: ar-OM-AyshaNeural
242 | Gender: Female
243 |
244 | Name: ar-QA-AmalNeural
245 | Gender: Female
246 |
247 | Name: ar-QA-MoazNeural
248 | Gender: Male
249 |
250 | Name: ar-SA-HamedNeural
251 | Gender: Male
252 |
253 | Name: ar-SA-ZariyahNeural
254 | Gender: Female
255 |
256 | Name: ar-SY-AmanyNeural
257 | Gender: Female
258 |
259 | Name: ar-SY-LaithNeural
260 | Gender: Male
261 |
262 | Name: ar-TN-HediNeural
263 | Gender: Male
264 |
265 | Name: ar-TN-ReemNeural
266 | Gender: Female
267 |
268 | Name: ar-YE-MaryamNeural
269 | Gender: Female
270 |
271 | Name: ar-YE-SalehNeural
272 | Gender: Male
273 |
274 | Name: az-AZ-BabekNeural
275 | Gender: Male
276 |
277 | Name: az-AZ-BanuNeural
278 | Gender: Female
279 |
280 | Name: bg-BG-BorislavNeural
281 | Gender: Male
282 |
283 | Name: bg-BG-KalinaNeural
284 | Gender: Female
285 |
286 | Name: bn-BD-NabanitaNeural
287 | Gender: Female
288 |
289 | Name: bn-BD-PradeepNeural
290 | Gender: Male
291 |
292 | Name: bn-IN-BashkarNeural
293 | Gender: Male
294 |
295 | Name: bn-IN-TanishaaNeural
296 | Gender: Female
297 |
298 | Name: bs-BA-GoranNeural
299 | Gender: Male
300 |
301 | Name: bs-BA-VesnaNeural
302 | Gender: Female
303 |
304 | Name: ca-ES-EnricNeural
305 | Gender: Male
306 |
307 | Name: ca-ES-JoanaNeural
308 | Gender: Female
309 |
310 | Name: cs-CZ-AntoninNeural
311 | Gender: Male
312 |
313 | Name: cs-CZ-VlastaNeural
314 | Gender: Female
315 |
316 | Name: cy-GB-AledNeural
317 | Gender: Male
318 |
319 | Name: cy-GB-NiaNeural
320 | Gender: Female
321 |
322 | Name: da-DK-ChristelNeural
323 | Gender: Female
324 |
325 | Name: da-DK-JeppeNeural
326 | Gender: Male
327 |
328 | Name: de-AT-IngridNeural
329 | Gender: Female
330 |
331 | Name: de-AT-JonasNeural
332 | Gender: Male
333 |
334 | Name: de-CH-JanNeural
335 | Gender: Male
336 |
337 | Name: de-CH-LeniNeural
338 | Gender: Female
339 |
340 | Name: de-DE-AmalaNeural
341 | Gender: Female
342 |
343 | Name: de-DE-ConradNeural
344 | Gender: Male
345 |
346 | Name: de-DE-KatjaNeural
347 | Gender: Female
348 |
349 | Name: de-DE-KillianNeural
350 | Gender: Male
351 |
352 | Name: el-GR-AthinaNeural
353 | Gender: Female
354 |
355 | Name: el-GR-NestorasNeural
356 | Gender: Male
357 |
358 | Name: es-AR-ElenaNeural
359 | Gender: Female
360 |
361 | Name: es-AR-TomasNeural
362 | Gender: Male
363 |
364 | Name: es-BO-MarceloNeural
365 | Gender: Male
366 |
367 | Name: es-BO-SofiaNeural
368 | Gender: Female
369 |
370 | Name: es-CL-CatalinaNeural
371 | Gender: Female
372 |
373 | Name: es-CL-LorenzoNeural
374 | Gender: Male
375 |
376 | Name: es-CO-GonzaloNeural
377 | Gender: Male
378 |
379 | Name: es-CO-SalomeNeural
380 | Gender: Female
381 |
382 | Name: es-CR-JuanNeural
383 | Gender: Male
384 |
385 | Name: es-CR-MariaNeural
386 | Gender: Female
387 |
388 | Name: es-CU-BelkysNeural
389 | Gender: Female
390 |
391 | Name: es-CU-ManuelNeural
392 | Gender: Male
393 |
394 | Name: es-DO-EmilioNeural
395 | Gender: Male
396 |
397 | Name: es-DO-RamonaNeural
398 | Gender: Female
399 |
400 | Name: es-EC-AndreaNeural
401 | Gender: Female
402 |
403 | Name: es-EC-LuisNeural
404 | Gender: Male
405 |
406 | Name: es-ES-AlvaroNeural
407 | Gender: Male
408 |
409 | Name: es-ES-ElviraNeural
410 | Gender: Female
411 |
412 | Name: es-GQ-JavierNeural
413 | Gender: Male
414 |
415 | Name: es-GQ-TeresaNeural
416 | Gender: Female
417 |
418 | Name: es-GT-AndresNeural
419 | Gender: Male
420 |
421 | Name: es-GT-MartaNeural
422 | Gender: Female
423 |
424 | Name: es-HN-CarlosNeural
425 | Gender: Male
426 |
427 | Name: es-HN-KarlaNeural
428 | Gender: Female
429 |
430 | Name: es-MX-DaliaNeural
431 | Gender: Female
432 |
433 | Name: es-MX-JorgeNeural
434 | Gender: Male
435 |
436 | Name: es-NI-FedericoNeural
437 | Gender: Male
438 |
439 | Name: es-NI-YolandaNeural
440 | Gender: Female
441 |
442 | Name: es-PA-MargaritaNeural
443 | Gender: Female
444 |
445 | Name: es-PA-RobertoNeural
446 | Gender: Male
447 |
448 | Name: es-PE-AlexNeural
449 | Gender: Male
450 |
451 | Name: es-PE-CamilaNeural
452 | Gender: Female
453 |
454 | Name: es-PR-KarinaNeural
455 | Gender: Female
456 |
457 | Name: es-PR-VictorNeural
458 | Gender: Male
459 |
460 | Name: es-PY-MarioNeural
461 | Gender: Male
462 |
463 | Name: es-PY-TaniaNeural
464 | Gender: Female
465 |
466 | Name: es-SV-LorenaNeural
467 | Gender: Female
468 |
469 | Name: es-SV-RodrigoNeural
470 | Gender: Male
471 |
472 | Name: es-US-AlonsoNeural
473 | Gender: Male
474 |
475 | Name: es-US-PalomaNeural
476 | Gender: Female
477 |
478 | Name: es-UY-MateoNeural
479 | Gender: Male
480 |
481 | Name: es-UY-ValentinaNeural
482 | Gender: Female
483 |
484 | Name: es-VE-PaolaNeural
485 | Gender: Female
486 |
487 | Name: es-VE-SebastianNeural
488 | Gender: Male
489 |
490 | Name: et-EE-AnuNeural
491 | Gender: Female
492 |
493 | Name: et-EE-KertNeural
494 | Gender: Male
495 |
496 | Name: fa-IR-DilaraNeural
497 | Gender: Female
498 |
499 | Name: fa-IR-FaridNeural
500 | Gender: Male
501 |
502 | Name: fi-FI-HarriNeural
503 | Gender: Male
504 |
505 | Name: fi-FI-NooraNeural
506 | Gender: Female
507 |
508 | Name: fil-PH-AngeloNeural
509 | Gender: Male
510 |
511 | Name: fil-PH-BlessicaNeural
512 | Gender: Female
513 |
514 | Name: fr-BE-CharlineNeural
515 | Gender: Female
516 |
517 | Name: fr-BE-GerardNeural
518 | Gender: Male
519 |
520 | Name: fr-CA-AntoineNeural
521 | Gender: Male
522 |
523 | Name: fr-CA-JeanNeural
524 | Gender: Male
525 |
526 | Name: fr-CA-SylvieNeural
527 | Gender: Female
528 |
529 | Name: fr-CH-ArianeNeural
530 | Gender: Female
531 |
532 | Name: fr-CH-FabriceNeural
533 | Gender: Male
534 |
535 | Name: fr-FR-DeniseNeural
536 | Gender: Female
537 |
538 | Name: fr-FR-EloiseNeural
539 | Gender: Female
540 |
541 | Name: fr-FR-HenriNeural
542 | Gender: Male
543 |
544 | Name: ga-IE-ColmNeural
545 | Gender: Male
546 |
547 | Name: ga-IE-OrlaNeural
548 | Gender: Female
549 |
550 | Name: gl-ES-RoiNeural
551 | Gender: Male
552 |
553 | Name: gl-ES-SabelaNeural
554 | Gender: Female
555 |
556 | Name: gu-IN-DhwaniNeural
557 | Gender: Female
558 |
559 | Name: gu-IN-NiranjanNeural
560 | Gender: Male
561 |
562 | Name: he-IL-AvriNeural
563 | Gender: Male
564 |
565 | Name: he-IL-HilaNeural
566 | Gender: Female
567 |
568 | Name: hi-IN-MadhurNeural
569 | Gender: Male
570 |
571 | Name: hi-IN-SwaraNeural
572 | Gender: Female
573 |
574 | Name: hr-HR-GabrijelaNeural
575 | Gender: Female
576 |
577 | Name: hr-HR-SreckoNeural
578 | Gender: Male
579 |
580 | Name: hu-HU-NoemiNeural
581 | Gender: Female
582 |
583 | Name: hu-HU-TamasNeural
584 | Gender: Male
585 |
586 | Name: id-ID-ArdiNeural
587 | Gender: Male
588 |
589 | Name: id-ID-GadisNeural
590 | Gender: Female
591 |
592 | Name: is-IS-GudrunNeural
593 | Gender: Female
594 |
595 | Name: is-IS-GunnarNeural
596 | Gender: Male
597 |
598 | Name: it-IT-DiegoNeural
599 | Gender: Male
600 |
601 | Name: it-IT-ElsaNeural
602 | Gender: Female
603 |
604 | Name: it-IT-IsabellaNeural
605 | Gender: Female
606 |
607 | Name: jv-ID-DimasNeural
608 | Gender: Male
609 |
610 | Name: jv-ID-SitiNeural
611 | Gender: Female
612 |
613 | Name: ka-GE-EkaNeural
614 | Gender: Female
615 |
616 | Name: ka-GE-GiorgiNeural
617 | Gender: Male
618 |
619 | Name: kk-KZ-AigulNeural
620 | Gender: Female
621 |
622 | Name: kk-KZ-DauletNeural
623 | Gender: Male
624 |
625 | Name: km-KH-PisethNeural
626 | Gender: Male
627 |
628 | Name: km-KH-SreymomNeural
629 | Gender: Female
630 |
631 | Name: kn-IN-GaganNeural
632 | Gender: Male
633 |
634 | Name: kn-IN-SapnaNeural
635 | Gender: Female
636 |
637 | Name: ko-KR-InJoonNeural
638 | Gender: Male
639 |
640 | Name: ko-KR-SunHiNeural
641 | Gender: Female
642 |
643 | Name: lo-LA-ChanthavongNeural
644 | Gender: Male
645 |
646 | Name: lo-LA-KeomanyNeural
647 | Gender: Female
648 |
649 | Name: lt-LT-LeonasNeural
650 | Gender: Male
651 |
652 | Name: lt-LT-OnaNeural
653 | Gender: Female
654 |
655 | Name: lv-LV-EveritaNeural
656 | Gender: Female
657 |
658 | Name: lv-LV-NilsNeural
659 | Gender: Male
660 |
661 | Name: mk-MK-AleksandarNeural
662 | Gender: Male
663 |
664 | Name: mk-MK-MarijaNeural
665 | Gender: Female
666 |
667 | Name: ml-IN-MidhunNeural
668 | Gender: Male
669 |
670 | Name: ml-IN-SobhanaNeural
671 | Gender: Female
672 |
673 | Name: mn-MN-BataaNeural
674 | Gender: Male
675 |
676 | Name: mn-MN-YesuiNeural
677 | Gender: Female
678 |
679 | Name: mr-IN-AarohiNeural
680 | Gender: Female
681 |
682 | Name: mr-IN-ManoharNeural
683 | Gender: Male
684 |
685 | Name: ms-MY-OsmanNeural
686 | Gender: Male
687 |
688 | Name: ms-MY-YasminNeural
689 | Gender: Female
690 |
691 | Name: mt-MT-GraceNeural
692 | Gender: Female
693 |
694 | Name: mt-MT-JosephNeural
695 | Gender: Male
696 |
697 | Name: my-MM-NilarNeural
698 | Gender: Female
699 |
700 | Name: my-MM-ThihaNeural
701 | Gender: Male
702 |
703 | Name: nb-NO-FinnNeural
704 | Gender: Male
705 |
706 | Name: nb-NO-PernilleNeural
707 | Gender: Female
708 |
709 | Name: ne-NP-HemkalaNeural
710 | Gender: Female
711 |
712 | Name: ne-NP-SagarNeural
713 | Gender: Male
714 |
715 | Name: nl-BE-ArnaudNeural
716 | Gender: Male
717 |
718 | Name: nl-BE-DenaNeural
719 | Gender: Female
720 |
721 | Name: nl-NL-ColetteNeural
722 | Gender: Female
723 |
724 | Name: nl-NL-FennaNeural
725 | Gender: Female
726 |
727 | Name: nl-NL-MaartenNeural
728 | Gender: Male
729 |
730 | Name: pl-PL-MarekNeural
731 | Gender: Male
732 |
733 | Name: pl-PL-ZofiaNeural
734 | Gender: Female
735 |
736 | Name: ps-AF-GulNawazNeural
737 | Gender: Male
738 |
739 | Name: ps-AF-LatifaNeural
740 | Gender: Female
741 |
742 | Name: pt-BR-AntonioNeural
743 | Gender: Male
744 |
745 | Name: pt-BR-FranciscaNeural
746 | Gender: Female
747 |
748 | Name: pt-PT-DuarteNeural
749 | Gender: Male
750 |
751 | Name: pt-PT-RaquelNeural
752 | Gender: Female
753 |
754 | Name: ro-RO-AlinaNeural
755 | Gender: Female
756 |
757 | Name: ro-RO-EmilNeural
758 | Gender: Male
759 |
760 | Name: ru-RU-DmitryNeural
761 | Gender: Male
762 |
763 | Name: ru-RU-SvetlanaNeural
764 | Gender: Female
765 |
766 | Name: si-LK-SameeraNeural
767 | Gender: Male
768 |
769 | Name: si-LK-ThiliniNeural
770 | Gender: Female
771 |
772 | Name: sk-SK-LukasNeural
773 | Gender: Male
774 |
775 | Name: sk-SK-ViktoriaNeural
776 | Gender: Female
777 |
778 | Name: sl-SI-PetraNeural
779 | Gender: Female
780 |
781 | Name: sl-SI-RokNeural
782 | Gender: Male
783 |
784 | Name: so-SO-MuuseNeural
785 | Gender: Male
786 |
787 | Name: so-SO-UbaxNeural
788 | Gender: Female
789 |
790 | Name: sq-AL-AnilaNeural
791 | Gender: Female
792 |
793 | Name: sq-AL-IlirNeural
794 | Gender: Male
795 |
796 | Name: sr-RS-NicholasNeural
797 | Gender: Male
798 |
799 | Name: sr-RS-SophieNeural
800 | Gender: Female
801 |
802 | Name: su-ID-JajangNeural
803 | Gender: Male
804 |
805 | Name: su-ID-TutiNeural
806 | Gender: Female
807 |
808 | Name: sv-SE-MattiasNeural
809 | Gender: Male
810 |
811 | Name: sv-SE-SofieNeural
812 | Gender: Female
813 |
814 | Name: sw-KE-RafikiNeural
815 | Gender: Male
816 |
817 | Name: sw-KE-ZuriNeural
818 | Gender: Female
819 |
820 | Name: sw-TZ-DaudiNeural
821 | Gender: Male
822 |
823 | Name: sw-TZ-RehemaNeural
824 | Gender: Female
825 |
826 | Name: ta-IN-PallaviNeural
827 | Gender: Female
828 |
829 | Name: ta-IN-ValluvarNeural
830 | Gender: Male
831 |
832 | Name: ta-LK-KumarNeural
833 | Gender: Male
834 |
835 | Name: ta-LK-SaranyaNeural
836 | Gender: Female
837 |
838 | Name: ta-MY-KaniNeural
839 | Gender: Female
840 |
841 | Name: ta-MY-SuryaNeural
842 | Gender: Male
843 |
844 | Name: ta-SG-AnbuNeural
845 | Gender: Male
846 |
847 | Name: ta-SG-VenbaNeural
848 | Gender: Female
849 |
850 | Name: te-IN-MohanNeural
851 | Gender: Male
852 |
853 | Name: te-IN-ShrutiNeural
854 | Gender: Female
855 |
856 | Name: th-TH-NiwatNeural
857 | Gender: Male
858 |
859 | Name: th-TH-PremwadeeNeural
860 | Gender: Female
861 |
862 | Name: tr-TR-AhmetNeural
863 | Gender: Male
864 |
865 | Name: tr-TR-EmelNeural
866 | Gender: Female
867 |
868 | Name: uk-UA-OstapNeural
869 | Gender: Male
870 |
871 | Name: uk-UA-PolinaNeural
872 | Gender: Female
873 |
874 | Name: ur-IN-GulNeural
875 | Gender: Female
876 |
877 | Name: ur-IN-SalmanNeural
878 | Gender: Male
879 |
880 | Name: ur-PK-AsadNeural
881 | Gender: Male
882 |
883 | Name: ur-PK-UzmaNeural
884 | Gender: Female
885 |
886 | Name: uz-UZ-MadinaNeural
887 | Gender: Female
888 |
889 | Name: uz-UZ-SardorNeural
890 | Gender: Male
891 |
892 | Name: vi-VN-HoaiMyNeural
893 | Gender: Female
894 |
895 | Name: vi-VN-NamMinhNeural
896 | Gender: Male
897 |
898 | Name: zu-ZA-ThandoNeural
899 | Gender: Female
900 |
901 | Name: zu-ZA-ThembaNeural
902 | Gender: Male
903 |
--------------------------------------------------------------------------------
/VTubeStudioPlugin/VTubeStudioPlugin.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4 | import asyncio
5 | import threading
6 | import random
7 |
8 | import pyvts
9 |
10 | from My_Tools.AIVT_print import aprint
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | AIVT_VTSP_Status = {
22 | "authenticated": False,
23 | "hotkey_trigger": False,
24 | }
25 |
26 | AIVT_hotkeys_parameters = {
27 | "trigger_first": False,
28 | "Emo_state_categories": ["normal", "happy", "shy", "proud", "shock", "sad", "angry", "embarrass", "afraid", "confuse"],
29 | "sentiment_analysis": False,
30 | "sentiment_analysis_model": "gemini-1.0-pro",
31 | "idle_ani": "",
32 | "idle_ani_previous": "",
33 | "normal": True,
34 | "normal_kn": "",
35 | "happy": True,
36 | "happy_kn": "",
37 | "shy": True,
38 | "shy_kn": "",
39 | "proud": True,
40 | "proud_kn": "",
41 | "shock": True,
42 | "shock_kn": "",
43 | "sad": True,
44 | "sad_kn": "",
45 | "angry": True,
46 | "angry_kn": "",
47 | "embarrass": True,
48 | "embarrass_kn": "",
49 | "afraid": True,
50 | "afraid_kn": "",
51 | "confuse": True,
52 | "confuse_kn": "",
53 | }
54 |
55 | AIVT_previous_ani_exp = {
56 | "idle_ani": "",
57 | "talking_ani": "",
58 | "talking_exp": "",
59 | }
60 |
61 |
62 |
63 |
64 |
65 | def AIVT_VTSP_authenticated():
66 | global AIVT_VTSP_Status, aivt_vtsp
67 |
68 | authentication_token_path = "VTubeStudioPlugin/VTSP_authentication_token.txt"
69 |
70 | AIVT_VTSP_info = {
71 | "plugin_name":"AIVT_VTSP",
72 | "developer":"PKevin",
73 | "authentication_token_path":authentication_token_path
74 | }
75 |
76 | aivt_vtsp = pyvts.vts(plugin_info=AIVT_VTSP_info)
77 |
78 |
79 | if os.path.exists(authentication_token_path):
80 | try:
81 | asyncio.run(async_AIVT_VTSP_authenticated())
82 | AIVT_VTSP_Status["authenticated"] = True
83 | print("!!! VTSP Authenticated Success !!!")
84 |
85 | except:
86 | try:
87 | asyncio.run(async_AIVT_VTSP_get_token_and_authenticated())
88 | AIVT_VTSP_Status["authenticated"] = True
89 | print("!!! VTSP Authenticated Success !!!")
90 |
91 | except:
92 | AIVT_VTSP_Status["authenticated"] = False
93 | print("!!! VTSP Authenticated Fail !!!")
94 |
95 | else:
96 | try:
97 | asyncio.run(async_AIVT_VTSP_get_token_and_authenticated())
98 | AIVT_VTSP_Status["authenticated"] = True
99 | print("!!! VTSP Authenticated Success !!!")
100 |
101 | except:
102 | AIVT_VTSP_Status["authenticated"] = False
103 | print("!!! VTSP Authenticated Fail !!!")
104 |
105 |
106 | async def async_AIVT_VTSP_get_token_and_authenticated():
107 | global aivt_vtsp
108 |
109 | await aivt_vtsp.connect()
110 | await aivt_vtsp.request_authenticate_token()
111 | await aivt_vtsp.write_token()
112 | await aivt_vtsp.request_authenticate()
113 | await aivt_vtsp.close()
114 |
115 | async def async_AIVT_VTSP_authenticated():
116 | global aivt_vtsp
117 |
118 | await aivt_vtsp.connect()
119 | await aivt_vtsp.read_token()
120 | await aivt_vtsp.request_authenticate()
121 | await aivt_vtsp.close()
122 |
123 |
124 |
125 |
126 |
127 | lock_hnt = threading.Lock()
128 |
129 | def VTSP_Hotkey_Names_Trigger(hotkey_names_list=[], command=None):
130 | global aivt_vtsp
131 |
132 | with lock_hnt:
133 | if AIVT_VTSP_Status["authenticated"] and len(hotkey_names_list) > 0:
134 | aprint(f"VTSP trigger hotkey names: {hotkey_names_list}")
135 | asyncio.run(async_VTSP_Hotkey_Names_Trigger(hotkey_names_list, command=command))
136 |
137 |
138 | async def async_VTSP_Hotkey_Names_Trigger(hotkey_names, command=None):
139 | global aivt_vtsp
140 |
141 | await aivt_vtsp.connect()
142 | await aivt_vtsp.request_authenticate()
143 |
144 | for hotkey_name in hotkey_names:
145 | try:
146 | send_hotkey_request = aivt_vtsp.vts_request.requestTriggerHotKey(hotkey_name)
147 | await aivt_vtsp.request(send_hotkey_request)
148 |
149 | except Exception as e:
150 | aprint(f"!!! Trigger Hotkey *{hotkey_name}* Fail !!!")
151 |
152 | await aivt_vtsp.close()
153 |
154 |
155 |
156 |
157 |
158 | def get_hotkey_names(text_kn, command=None):
159 | global AIVT_hotkeys_parameters
160 |
161 | items = [item.strip() for item in text_kn.split("/") if item.strip()]
162 | list1, list2, list3, list4 = [], [], [], []
163 |
164 | for item in items:
165 | if item.startswith("!"):
166 | list1.append(item[1:].strip())
167 |
168 | elif item.startswith("*"):
169 | list2.append(item[1:].strip())
170 |
171 | elif item.startswith("@"):
172 | list3.append(item[1:].strip())
173 |
174 | else:
175 | list4.append(item)
176 |
177 |
178 | all = []
179 | r_ani = []
180 | f_exp = []
181 | r_iexp = []
182 | r_exp = []
183 |
184 | if list1:
185 | if command == "idle_ani" and len(list1) > 1:
186 | i = 0
187 |
188 | while True:
189 | i += 10
190 | kn = random.choice(list1)
191 | if kn != AIVT_hotkeys_parameters["idle_ani_previous"]:
192 | break
193 |
194 | if i > 10:
195 | break
196 |
197 | else:
198 | kn = random.choice(list1)
199 |
200 | AIVT_hotkeys_parameters["idle_ani_previous"] = kn
201 |
202 | all.append(kn)
203 | r_ani.append(kn)
204 |
205 | if list2:
206 | for name in list2:
207 | if not name in all:
208 | all.append(name)
209 | f_exp.append(name)
210 |
211 | if list3:
212 | list3_r = []
213 | for name in list3:
214 | if not name in all:
215 | list3_r.append(name)
216 |
217 | if list3_r:
218 | kn = random.choice(list3_r)
219 | all.append(kn)
220 | r_iexp.append(kn)
221 |
222 | if list4:
223 | list4_r = []
224 | for name in list4:
225 | if not name in all:
226 | list4_r.append(name)
227 |
228 | if list4_r:
229 | kn = random.choice(list4)
230 | all.append(kn)
231 | r_exp.append(kn)
232 |
233 |
234 | result_list = {
235 | "all": all,
236 | "all_exp": f_exp+r_iexp+r_exp,
237 | "r_ani": r_ani,
238 | "f_exp": f_exp,
239 | "r_iexp": r_iexp,
240 | "r_exp": r_exp,
241 | }
242 |
243 | return result_list
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | if __name__ == "__main__":
255 | AIVT_VTSP_authenticated()
256 | AIVT_VTSP_Status["authenticated"] = True
257 |
258 | while AIVT_VTSP_Status["authenticated"]:
259 | input_text = input("Enter the names of hotkey (split by /) :\n")
260 | hotkey_names = [name.strip() for name in input_text.split("/") if name.strip()]
261 | VTSP_Hotkey_Names_Trigger(hotkey_names_list=hotkey_names)
262 |
263 |
264 |
265 |
266 |
--------------------------------------------------------------------------------
/VTubeStudioPlugin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AIVTDevPKevin/AI-VTuber-System/a7ab32694680c3bcb0d84eceecf3080e0261a7e0/VTubeStudioPlugin/__init__.py
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | edge_tts==6.1.10
2 | google-generativeai==0.5.3
3 | keyboard==0.13.5
4 | obs-websocket-py==1.0
5 | openai==1.13.3
6 | openai-whisper==20231117
7 | protobuf==4.25.3
8 | pyaudio==0.2.14
9 | pydub==0.25.1
10 | pyside6==6.6.2
11 | pytchat==0.5.5
12 | pyvts==0.3.2
13 | Requests==2.31.0
14 | tiktoken==0.7.0
15 | transformers>=4.48.0
16 | twitchio==2.9.1
17 |
--------------------------------------------------------------------------------