助理版多处优化

1、解决多声道麦克风兼容问题;
2、重构fay_core.py及fay_booter.py代码;
3、ui适应布局调整;
4、恢复男女声音选择;
5、”思考中...“显示逻辑修复。
This commit is contained in:
xszyou 2023-06-14 20:34:36 +08:00
parent ae1d2ae292
commit 7c67bb5858
11 changed files with 104 additions and 472 deletions

View File

@ -28,6 +28,8 @@ UE5工程https://github.com/xszyou/fay-ue5
![](images/controller.png) ![](images/controller.png)
助理版Fay控制器使用语音沟通语音和文字回复文字沟通文字回复。
### **PC远程助理** [`PC demo`](https://github.com/TheRamU/Fay/tree/main/python_connector_demo) ### **PC远程助理** [`PC demo`](https://github.com/TheRamU/Fay/tree/main/python_connector_demo)
@ -45,9 +47,12 @@ UE5工程https://github.com/xszyou/fay-ue5
下载工程: [https://pan.baidu.com/s/1RBo2Pie6A5yTrCf1cn_Tuw?pwd=ck99](https://pan.baidu.com/s/1RBo2Pie6A5yTrCf1cn_Tuw?pwd=ck99) 下载工程: [https://pan.baidu.com/s/1RBo2Pie6A5yTrCf1cn_Tuw?pwd=ck99](https://pan.baidu.com/s/1RBo2Pie6A5yTrCf1cn_Tuw?pwd=ck99)
下载windows运行包: [https://pan.baidu.com/s/1CsJ647uV5rS2NjQH3QT0Iw?pwd=s9s8](https://pan.baidu.com/s/1CsJ647uV5rS2NjQH3QT0Iw?pwd=s9s8) 下载windows运行包: [https://pan.baidu.com/s/1CsJ647uV5rS2NjQH3QT0Iw?pwd=s9s8](https://pan.baidu.com/s/1CsJ647uV5rS2NjQH3QT0Iw?pwd=s9s8)
![](images/UElucky.png) ![](images/UElucky.png)
工程及运行包https://github.com/xszyou/fay-ue5 工程及运行包https://github.com/xszyou/fay-ue5
@ -105,7 +110,6 @@ UE5工程https://github.com/xszyou/fay-ue5
│   ├── fay_core.py # 数字人核心模块 │   ├── fay_core.py # 数字人核心模块
│   ├── recorder.py # 录音器 │   ├── recorder.py # 录音器
│   ├── tts_voice.py # 语音生源枚举 │   ├── tts_voice.py # 语音生源枚举
│   ├── viewer.py # 抖音直播间接入模块
│   └── wsa_server.py # WebSocket 服务端 │   └── wsa_server.py # WebSocket 服务端
├── gui # 图形界面 ├── gui # 图形界面
│   ├── flask_server.py # Flask 服务端 │   ├── flask_server.py # Flask 服务端
@ -124,6 +128,14 @@ UE5工程https://github.com/xszyou/fay-ue5
## **三、升级日志** ## **三、升级日志**
**2023.06.14**
+ 解决多声道麦克风兼容问题;
+ 重构fay_core.py及fay_booter.py代码
+ ui适应布局调整
+ 恢复声音选择;
+ ”思考中...“显示逻辑修复。
**2023.05.27** **2023.05.27**
+ 修复多个bug消息框换行及空格问题、语音识别优化 + 修复多个bug消息框换行及空格问题、语音识别优化

View File

@ -1,13 +1,13 @@
{ {
"attribute": { "attribute": {
"age": "\u6210\u5e74", "age": "\u6210\u5e74",
"birth": "\u4e2d\u56fd", "birth": "Github",
"constellation": "\u6c34\u74f6\u5ea7", "constellation": "\u6c34\u74f6\u5ea7",
"contact": "qq467665317", "contact": "qq467665317",
"gender": "\u7537", "gender": "\u5973",
"hobby": "\u53d1\u5446", "hobby": "\u53d1\u5446",
"job": "\u4ea7\u54c1\u5e03\u9053\u8005", "job": "\u52a9\u7406",
"name": "\u9648\u5347", "name": "\u83f2\u83f2",
"voice": "XIAO_XIAO", "voice": "XIAO_XIAO",
"zodiac": "\u86c7" "zodiac": "\u86c7"
}, },
@ -15,18 +15,19 @@
"QnA": "qa_demo.xlsx", "QnA": "qa_demo.xlsx",
"maxInteractTime": 15, "maxInteractTime": 15,
"perception": { "perception": {
"chat": 7, "chat": 10,
"follow": 10, "follow": 10,
"gift": 50, "gift": 10,
"indifferent": 10, "indifferent": 10,
"join": 10 "join": 10
}, },
"playSound": true "playSound": true,
"visualization": false
}, },
"items": [ "items": [
{ {
"QnA": "qa_demo.xlsx", "QnA": "",
"demoVideo": "C:/Demo.mp4", "demoVideo": "",
"enabled": false, "enabled": false,
"explain": { "explain": {
"character": "", "character": "",

View File

@ -35,11 +35,10 @@ from ai_module import nlp_VisualGLM as VisualGLM
#文本消息处理 #文本消息处理
def send_for_answer(msg,sendto): def send_for_answer(msg,sendto):
contentdb = Content_Db() contentdb = Content_Db()
contentdb.add_content('member','send', msg) contentdb.add_content('member','send',msg)
text = '' text = ''
textlist = [] textlist = []
try: try:
#wsa_server.get_web_instance().add_cmd({"panelMsg": "思考中..."})
util.log(1, '自然语言处理...') util.log(1, '自然语言处理...')
tm = time.time() tm = time.time()
cfg.load_config() cfg.load_config()
@ -54,23 +53,21 @@ def send_for_answer(msg,sendto):
text = chatgpt.question(msg) text = chatgpt.question(msg)
elif cfg.key_chat_module == 'rasa': elif cfg.key_chat_module == 'rasa':
textlist = nlp_rasa.question(msg) textlist = nlp_rasa.question(msg)
text = textlist[0]['text'] text = textlist[0]['text']
elif cfg.key_chat_module == "VisualGLM": elif cfg.key_chat_module == "VisualGLM":
text = VisualGLM.question(msg) text = VisualGLM.question(msg)
else: else:
raise RuntimeError('讯飞key、yuan key、chatgpt key都没有配置') raise RuntimeError('讯飞key、yuan key、chatgpt key都没有配置')
util.log(1, '自然语言处理完成. 耗时: {} ms'.format(math.floor((time.time() - tm) * 1000))) util.log(1, '自然语言处理完成. 耗时: {} ms'.format(math.floor((time.time() - tm) * 1000)))
if text == '哎呀,你这么说我也不懂,详细点呗' or text == '': if text == '哎呀,你这么说我也不懂,详细点呗' or text == '':
util.log(1, '[!] 自然语言无语了!') util.log(1, '[!] 自然语言无语了!')
text = '哎呀,你这么说我也不懂,详细点呗' text = '哎呀,你这么说我也不懂,详细点呗'
# wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
except BaseException as e: except BaseException as e:
print(e) print(e)
util.log(1, '自然语言处理错误!') util.log(1, '自然语言处理错误!')
text = '哎呀,你这么说我也不懂,详细点呗' text = '哎呀,你这么说我也不懂,详细点呗'
# wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
now = datetime.now() now = datetime.now()
timetext = str(now.strftime("%Y-%m-%d %H:%M:%S")) timetext = str(now.strftime("%Y-%m-%d %H:%M:%S"))
@ -183,10 +180,10 @@ class FeiFei:
self.playing = False self.playing = False
wsa_server.get_web_instance().add_cmd({"panelMsg": ""}) wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
#检查是否命中指令或q&a
def __get_answer(self, interleaver, text): def __get_answer(self, interleaver, text):
if interleaver == "mic": if interleaver == "mic":
# #
keyword = self.__get_keyword(self.command_keyword, text) keyword = self.__get_keyword(self.command_keyword, text)
if keyword is not None: if keyword is not None:
if keyword == "playSong": if keyword == "playSong":
@ -225,34 +222,7 @@ class FeiFei:
answer = self.__get_keyword(self.__read_qna(config_util.config['interact']['QnA']), text) answer = self.__get_keyword(self.__read_qna(config_util.config['interact']['QnA']), text)
if answer is not None: if answer is not None:
return answer return answer
items = self.__get_item_list()
if len(items) > 0:
item = items[self.item_index]
# 跨商品物品问答匹配
for ite in items:
name = ite["name"]
if name != item["name"]:
if name in text or self.__string_similar(text, name) > 0.6:
item = ite
break
# 商品介绍问答
keyword = self.__get_keyword(self.explain_keyword, text)
if keyword is not None:
try:
return item["explain"][keyword]
except BaseException as e:
print(e)
# 商品问答
answer = self.__get_keyword(self.__read_qna(item["QnA"]), text)
if answer is not None:
return answer
return None
def __get_list_answer(self, answers, text): def __get_list_answer(self, answers, text):
last_similar = 0 last_similar = 0
@ -272,26 +242,19 @@ class FeiFei:
return None return None
def __auto_speak(self): def __auto_speak(self):
i = 0
script_index = 0
while self.__running: while self.__running:
time.sleep(0.8) time.sleep(0.8)
if self.speaking or self.sleep: if self.speaking or self.sleep:
continue continue
try: try:
# 简化逻辑:默认执行带货脚本,带货脚本执行其间有人互动,则执行完当前脚本就回应最后三条互动,回应完继续执行带货脚本 if len(self.interactive) > 0:
if i <= 3 and len(self.interactive) > i: interact: Interact = self.interactive.pop()
i += 1
interact: Interact = self.interactive[0 - i]
if interact.interact_type == 1:
self.q_msg = interact.data["msg"]
index = interact.interact_type index = interact.interact_type
# print("index:{0}".format(index))
user_name = interact.data["user"]
# self.__isExecute = True #!!!!
if index == 1: if index == 1:
self.q_msg = interact.data["msg"]
#fay eyes
fay_eyes = yolov8.new_instance() fay_eyes = yolov8.new_instance()
if fay_eyes.get_status():#YOLO正在运行 if fay_eyes.get_status():#YOLO正在运行
person_count, stand_count, sit_count = fay_eyes.get_counts() person_count, stand_count, sit_count = fay_eyes.get_counts()
@ -299,15 +262,14 @@ class FeiFei:
wsa_server.get_web_instance().add_cmd({"panelMsg": "不是有且只有一个人,不互动"}) wsa_server.get_web_instance().add_cmd({"panelMsg": "不是有且只有一个人,不互动"})
continue continue
answer = self.__get_answer(interact.interleaver, self.q_msg) answer = self.__get_answer(interact.interleaver, self.q_msg)#确定是否命中指令或q&a
if(self.muting): #静音指令正在执行 if(self.muting): #静音指令正在执行
wsa_server.get_web_instance().add_cmd({"panelMsg": "静音指令正在执行,不互动"}) wsa_server.get_web_instance().add_cmd({"panelMsg": "静音指令正在执行,不互动"})
continue continue
contentdb = Content_Db() contentdb = Content_Db()
contentdb.add_content('member','speak',self.q_msg) contentdb.add_content('member','speak',self.q_msg)
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"member","content":self.q_msg}}) wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"member","content":self.q_msg}})
text = '' text = ''
textlist = [] textlist = []
if answer is None: if answer is None:
@ -327,7 +289,6 @@ class FeiFei:
text = textlist[0]['text'] text = textlist[0]['text']
elif cfg.key_chat_module == "VisualGLM": elif cfg.key_chat_module == "VisualGLM":
text = VisualGLM.question(self.q_msg) text = VisualGLM.question(self.q_msg)
else: else:
raise RuntimeError('讯飞key、yuan key、chatgpt key都没有配置') raise RuntimeError('讯飞key、yuan key、chatgpt key都没有配置')
util.log(1, '自然语言处理完成. 耗时: {} ms'.format(math.floor((time.time() - tm) * 1000))) util.log(1, '自然语言处理完成. 耗时: {} ms'.format(math.floor((time.time() - tm) * 1000)))
@ -340,14 +301,9 @@ class FeiFei:
util.log(1, '自然语言处理错误!') util.log(1, '自然语言处理错误!')
wsa_server.get_web_instance().add_cmd({"panelMsg": ""}) wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
continue continue
elif answer != 'NO_ANSWER': elif answer != 'NO_ANSWER': #语音内容没有命中指令,回复q&a内容
text = answer text = answer
self.a_msg = text
if len(user_name) == 0:
self.a_msg = text
else:
self.a_msg = user_name + '' + text
contentdb.add_content('fay','speak',self.a_msg) contentdb.add_content('fay','speak',self.a_msg)
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":self.a_msg}}) wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":self.a_msg}})
if len(textlist) > 1: if len(textlist) > 1:
@ -356,130 +312,19 @@ class FeiFei:
contentdb.add_content('fay','speak',textlist[i]['text']) contentdb.add_content('fay','speak',textlist[i]['text'])
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":textlist[i]['text']}}) wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":textlist[i]['text']}})
i+= 1 i+= 1
wsa_server.get_web_instance().add_cmd({"panelMsg": self.a_msg})
elif index == 2:
self.a_msg = ['我们的直播间越来越多人咯', '感谢{}的到来'.format(user_name), '欢印{}来到我们的直播间'.format(user_name)][
random.randint(0, 2)]
elif index == 3:
gift = interact.data["gift"]
self.a_msg = '感谢感谢,感谢 {}送给我的{}{}'.format(interact.data["user"], interact.data["amount"], gift[1])
elif index == 4:
self.a_msg = '感谢关注'
elif index == 5:
msg = ""
for i in range(0, len(interact.data["gifts"])):
user = interact.data["gifts"][i]["user"]
gift = interact.data["gifts"][i]["gift"]
amount = interact.data["gifts"][i]["amount"]
msg += "{}送给我的{}{}".format(user, amount, gift[1])
self.a_msg = '感谢感谢,感谢' + msg
self.last_speak_data = self.a_msg self.last_speak_data = self.a_msg
self.speaking = True self.speaking = True
MyThread(target=self.__say, args=['interact']).start() MyThread(target=self.__say, args=['interact']).start()
else:
i = 0
self.interactive.clear()
config_items = config_util.config["items"]
items = []
for item in config_items:
if item["enabled"]:
items.append(item)
if len(items) > 0:
if self.item_index >= len(items):
self.item_index = 0
script_index = 0
item = items[self.item_index]
script_index = script_index + 1
explain_key = self.__get_explain_from_index(script_index)
if explain_key is None:
self.item_index = self.item_index + 1
script_index = 0
if self.item_index >= len(items):
self.item_index = 0
explain_key = self.__get_explain_from_index(script_index)
explain = item["explain"][explain_key]
if len(explain) > 0:
self.a_msg = explain
self.last_speak_data = self.a_msg
self.speaking = True
MyThread(target=self.__say, args=['script']).start()
except BaseException as e: except BaseException as e:
print(e) print(e)
def __get_item_list(self) -> list:
items = []
for item in config_util.config["items"]:
if item["enabled"]:
items.append(item)
return items
def __get_explain_from_index(self, index: int):
if index == 0:
return "character"
if index == 1:
return "discount"
if index == 2:
return "intro"
if index == 3:
return "price"
if index == 4:
return "promise"
if index == 5:
return "usage"
return None
def on_interact(self, interact: Interact): def on_interact(self, interact: Interact):
self.interactive.append(interact)
# 合并同类交互
# 进入
if interact.interact_type == 2:
itr = self.__get_interactive(2)
if itr is None:
self.interactive.append(interact)
else:
newItr = (2, itr.data["user"] + ', ' + interact.data["user"], itr.data["msg"])
self.interactive.remove(itr)
self.interactive.append(newItr)
# 送礼
elif interact.interact_type == 3:
gifts = []
rm_list = []
for itr in self.interactive:
if itr.interact_type == 3:
gifts.append({
"user": itr.data["user"],
"gift": itr.data["gift"],
"amount": itr.data["amount"]
})
rm_list.append(itr)
elif itr.interact_type == 5:
for gift in itr.data["gifts"]:
gifts.append(gift)
rm_list.append(itr)
if len(rm_list) > 0:
for itr in rm_list:
self.interactive.remove(itr)
self.interactive.append(Interact("live", 5, {"gifts": gifts}))
# 关注
elif interact.interact_type == 4:
if self.__get_interactive(2) is None:
self.interactive.append(interact)
else:
self.interactive.append(interact)
MyThread(target=self.__update_mood, args=[interact.interact_type]).start() MyThread(target=self.__update_mood, args=[interact.interact_type]).start()
MyThread(target=storer.storage_live_interact, args=[interact]).start() MyThread(target=storer.storage_live_interact, args=[interact]).start()
def __get_interactive(self, interactType) -> Interact:
for interact in self.interactive:
if interact is Interact and interact.interact_type == interactType:
return interact
return None
# 适应模型计算 # 适应模型计算
def __fay(self, index): def __fay(self, index):
@ -529,7 +374,7 @@ class FeiFei:
if self.mood <= -1: if self.mood <= -1:
self.mood = -1 self.mood = -1
def __get_mood(self): def __get_mood_voice(self):
voice = tts_voice.get_voice_of(config_util.config["attribute"]["voice"]) voice = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
if voice is None: if voice is None:
voice = EnumVoice.XIAO_XIAO voice = EnumVoice.XIAO_XIAO
@ -547,14 +392,13 @@ class FeiFei:
sayType = styleList["cheerful"] sayType = styleList["cheerful"]
return sayType return sayType
# 合成声音加上type代表是脚本还是互动 # 合成声音
def __say(self, styleType): def __say(self, styleType):
try: try:
if len(self.a_msg) < 1: if len(self.a_msg) < 1:
self.speaking = False self.speaking = False
else: else:
# print(self.__get_mood().name + self.a_msg) util.printInfo(1, '菲菲', '({}) {}'.format(self.__get_mood_voice(), self.a_msg))
util.printInfo(1, '菲菲', '({}) {}'.format(self.__get_mood(), self.a_msg))
MyThread(target=storer.storage_live_interact, args=[Interact('Fay', 0, {'user': 'Fay', 'msg': self.a_msg})]).start() MyThread(target=storer.storage_live_interact, args=[Interact('Fay', 0, {'user': 'Fay', 'msg': self.a_msg})]).start()
util.log(1, '合成音频...') util.log(1, '合成音频...')
tm = time.time() tm = time.time()
@ -562,14 +406,13 @@ class FeiFei:
if not config_util.config["interact"]["playSound"]: # 非展板播放 if not config_util.config["interact"]["playSound"]: # 非展板播放
content = {'Topic': 'Unreal', 'Data': {'Key': 'text', 'Value': self.a_msg}} content = {'Topic': 'Unreal', 'Data': {'Key': 'text', 'Value': self.a_msg}}
wsa_server.get_instance().add_cmd(content) wsa_server.get_instance().add_cmd(content)
result = self.sp.to_sample(self.a_msg, self.__get_mood()) result = self.sp.to_sample(self.a_msg, self.__get_mood_voice())
util.log(1, '合成音频完成. 耗时: {} ms 文件:{}'.format(math.floor((time.time() - tm) * 1000), result)) util.log(1, '合成音频完成. 耗时: {} ms 文件:{}'.format(math.floor((time.time() - tm) * 1000), result))
if result is not None: if result is not None:
MyThread(target=self.__send_audio, args=[result, styleType]).start() MyThread(target=self.__send_or_play_audio, args=[result, styleType]).start()
return result return result
except BaseException as e: except BaseException as e:
print(e) print(e)
# print("tts失败")
self.speaking = False self.speaking = False
return None return None
@ -579,7 +422,8 @@ class FeiFei:
pygame.mixer.music.load(file_url) pygame.mixer.music.load(file_url)
pygame.mixer.music.play() pygame.mixer.music.play()
def __send_audio(self, file_url, say_type):
def __send_or_play_audio(self, file_url, say_type):
try: try:
audio_length = eyed3.load(file_url).info.time_secs #mp3音频长度 audio_length = eyed3.load(file_url).info.time_secs #mp3音频长度
# with wave.open(file_url, 'rb') as wav_file: #wav音频长度 # with wave.open(file_url, 'rb') as wav_file: #wav音频长度
@ -605,14 +449,11 @@ class FeiFei:
util.log(1, "远程音频发送完成:{}".format(total)) util.log(1, "远程音频发送完成:{}".format(total))
except socket.error as serr: except socket.error as serr:
util.log(1,"远程音频输入输出设备已经断开:{}".format(serr)) util.log(1,"远程音频输入输出设备已经断开:{}".format(serr))
wsa_server.get_web_instance().add_cmd({"panelMsg": self.a_msg}) time.sleep(audio_length + 0.5)
time.sleep(audio_length + 0.5) wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
wsa_server.get_web_instance().add_cmd({"panelMsg": ""}) if config_util.config["interact"]["playSound"]:
if config_util.config["interact"]["playSound"]: util.log(1, '结束播放!')
util.log(1, '结束播放!')
self.speaking = False self.speaking = False
except Exception as e: except Exception as e:
print(e) print(e)
@ -642,50 +483,12 @@ class FeiFei:
except Exception as err: except Exception as err:
pass pass
def __waiting_speaking(self, file_url):
try:
time.sleep(5)
print('[' + str(int(time.time())) + '][菲菲] [S] [开始发言]')
with wave.open(file_url, 'rb') as wav_file:
wav_length = wav_file.getnframes() / float(wav_file.getframerate())
time.sleep(wav_length)
self.last_interact_time = time.time()
self.speaking = False
print('[' + str(int(time.time())) + '][菲菲] [E] [结束发言]')
time.sleep(30)
os.remove(file_url)
except:
self.last_interact_time = time.time()
self.speaking = False
# 冷场情绪更新
def __update_mood_runnable(self):
while self.__running:
time.sleep(10)
update = config_util.config["interact"]["perception"]["indifferent"] / 100
if len(self.interactive) < 1:
if self.mood > 0:
if self.mood > update:
self.mood = self.mood - update
else:
self.mood = 0
elif self.mood < 0:
if self.mood < -update:
self.mood = self.mood + update
else:
self.mood = 0
def set_sleep(self, sleep): def set_sleep(self, sleep):
self.sleep = sleep self.sleep = sleep
def start(self): def start(self):
MyThread(target=self.__send_mood).start() MyThread(target=self.__send_mood).start()
MyThread(target=self.__auto_speak).start() MyThread(target=self.__auto_speak).start()
MyThread(target=self.__update_mood_runnable).start()
def stop(self): def stop(self):
self.__running = False self.__running = False

View File

@ -9,8 +9,7 @@ from core import wsa_server
from scheduler.thread_manager import MyThread from scheduler.thread_manager import MyThread
from utils import util from utils import util
from utils import config_util as cfg from utils import config_util as cfg
import numpy as np
# 启动时间 (秒) # 启动时间 (秒)
_ATTACK = 0.2 _ATTACK = 0.2
@ -105,6 +104,11 @@ class Recorder:
data = stream.read(1024, exception_on_overflow=False) data = stream.read(1024, exception_on_overflow=False)
if not data: if not data:
continue continue
#只获取第一声道
data = np.frombuffer(data, dtype=np.int16)
data = np.reshape(data, (-1, cfg.config['source']['record']['channels'])) # reshaping the array to split the channels
mono = data[:, 0] # taking the first channel
data = mono.tobytes()
level = audioop.rms(data, 2) level = audioop.rms(data, 2)
if len(self.__history_data) >= 5: if len(self.__history_data) >= 5:
@ -147,10 +151,7 @@ class Recorder:
self.__waitingResult(self.__aLiNls) self.__waitingResult(self.__aLiNls)
if not soon and isSpeaking: if not soon and isSpeaking:
self.__aLiNls.send(data) self.__aLiNls.send(data)
def set_processing(self, processing): def set_processing(self, processing):
self.__processing = processing self.__processing = processing

View File

@ -1,126 +0,0 @@
from abc import abstractmethod
import json
import random
import time
import requests
import websocket
import ssl
from core.interact import Interact
from scheduler.thread_manager import MyThread
from utils import config_util, util
USER_URL = 'https://www.douyin.com/user/'
interact_datas = []
import json
import time
import ssl
import websocket
running = False
class WS_Client:
def __init__(self, host):
self.__ws = None
self.__host = host
self.__connect(host)
def on_message(self, ws, message):
global interact_datas
try:
data = json.loads(message)
if data["Type"] == 1:#留言
if len(interact_datas) >= 5:
interact_datas.pop()
interact = Interact("live", 1, {"user": json.loads(data["Data"])["User"]["Nickname"], "msg": json.loads(data["Data"])["Content"]})
interact_datas.append(interact)
if data["Type"] == 3:#进入
if len(interact_datas) >= 5:
interact_datas.pop()
interact_datas.append(Interact("live", 2, {"user": json.loads(data["Data"])["User"]["Nickname"], "msg": "来了"}))
#...
except Exception as e:
pass
def on_close(self, ws, code, msg):
pass
def on_error(self, ws, error):
util.log(1, "弹幕监听WebSocket error. Reconnecting...")
time.sleep(5)
self.__connect(self.__host)
def on_open(self, ws):
pass
def __connect(self, host):
global running
while running:
try:
self.__ws = websocket.WebSocketApp(host,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close)
self.__ws.on_open = self.on_open
self.__ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
util.log(1, "弹幕监听WebSocket success.")
break
except Exception as e:
break
def close(self):
self.__ws.close()
class Viewer:
def __init__(self):
global running
running = True
self.live_started = False
self.dy_msg_ws = None
def __start(self):
MyThread(target=self.__run_dy_msg_ws).start() #获取抖音监听内容
self.live_started = True
MyThread(target=self.__get_package_listen_interact_runnable).start()
def __run_dy_msg_ws(self):
self.dy_msg_ws = WS_Client('ws://127.0.0.1:8888')
def start(self):
MyThread(target=self.__start).start()
def is_live_started(self):
return self.live_started
#Add by xszyou on 20230412.通过抓包监测互动数据
def __get_package_listen_interact_runnable(self):
global interact_datas
global running
while running:
if not self.live_started:
continue
for interact in interact_datas:
MyThread(target=self.on_interact, args=[interact, time.time()]).start()
interact_datas.clear()
def stop(self):
global running
running = False
if self.dy_msg_ws:
self.dy_msg_ws.close()
self.dy_msg_ws = None
@abstractmethod
def on_interact(self, interact, event_time):
pass
@abstractmethod
def on_change_state(self, is_live_started):
pass

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,52 +1,17 @@
import time import time
from io import BytesIO
import socket
import pyaudio import pyaudio
import numpy as np
import scipy.io.wavfile as wav
import wave
from core.interact import Interact from core.interact import Interact
from core.recorder import Recorder from core.recorder import Recorder
from core.fay_core import FeiFei from core.fay_core import FeiFei
from core.viewer import Viewer
from scheduler.thread_manager import MyThread from scheduler.thread_manager import MyThread
from utils import util, config_util, stream_util, ngrok_util from utils import util, config_util, stream_util, ngrok_util
from core.wsa_server import MyServer from core.wsa_server import MyServer
feiFei: FeiFei = None feiFei: FeiFei = None
viewerListener: Viewer = None
recorderListener: Recorder = None recorderListener: Recorder = None
__running = True __running = True
class ViewerListener(Viewer):
def __init__(self):
super().__init__()
def on_interact(self, interact: Interact, event_time):
type_names = {
1: '发言',
2: '进入',
3: '送礼',
4: '关注'
}
util.printInfo(1, type_names[interact.interact_type], '{}: {}'.format(interact.data["user"], interact.data["msg"]), event_time)
if interact.interact_type == 1:
feiFei.last_quest_time = time.time()
thr = MyThread(target=feiFei.on_interact, args=[interact])
thr.start()
thr.join()
def on_change_state(self, is_live_started):
feiFei.set_sleep(not is_live_started)
pass
#录制麦克风音频输入并传给aliyun #录制麦克风音频输入并传给aliyun
class RecorderListener(Recorder): class RecorderListener(Recorder):
@ -54,7 +19,6 @@ class RecorderListener(Recorder):
self.__device = device self.__device = device
self.__RATE = 16000 self.__RATE = 16000
self.__FORMAT = pyaudio.paInt16 self.__FORMAT = pyaudio.paInt16
self.__CHANNELS = 1
super().__init__(fei) super().__init__(fei)
@ -67,19 +31,23 @@ class RecorderListener(Recorder):
def get_stream(self): def get_stream(self):
self.paudio = pyaudio.PyAudio() self.paudio = pyaudio.PyAudio()
device_id = self.__findInternalRecordingDevice(self.paudio) device_id,devInfo = self.__findInternalRecordingDevice(self.paudio)
if device_id < 0: if device_id < 0:
return return
self.stream = self.paudio.open(input_device_index=device_id, rate=self.__RATE, format=self.__FORMAT, channels=self.__CHANNELS, input=True) rate = int(devInfo['defaultSampleRate'])
channels = int(devInfo['maxInputChannels'])
self.stream = self.paudio.open(input_device_index=device_id, rate=self.__RATE, format=self.__FORMAT, channels=channels, input=True)
return self.stream return self.stream
def __findInternalRecordingDevice(self, p): def __findInternalRecordingDevice(self, p):
for i in range(p.get_device_count()): for i in range(p.get_device_count()):
devInfo = p.get_device_info_by_index(i) devInfo = p.get_device_info_by_index(i)
if devInfo['name'].find(self.__device) >= 0 and devInfo['hostApi'] == 0: if devInfo['name'].find(self.__device) >= 0 and devInfo['hostApi'] == 0:
return i config_util.config['source']['record']['channels'] = devInfo['maxInputChannels']
config_util.save_config(config_util.config)
return i, devInfo
util.log(1, '[!] 无法找到内录设备!') util.log(1, '[!] 无法找到内录设备!')
return -1 return -1, None
def stop(self): def stop(self):
super().stop() super().stop()
@ -119,6 +87,8 @@ class DeviceInputListener(Recorder):
time.sleep(1) time.sleep(1)
def on_speaking(self, text): def on_speaking(self, text):
global feiFei
if len(text) > 1: if len(text) > 1:
interact = Interact("mic", 1, {'user': '', 'msg': text}) interact = Interact("mic", 1, {'user': '', 'msg': text})
util.printInfo(3, "语音", '{}'.format(interact.data["msg"]), time.time()) util.printInfo(3, "语音", '{}'.format(interact.data["msg"]), time.time())
@ -146,12 +116,7 @@ class DeviceInputListener(Recorder):
def console_listener(): def console_listener():
type_names = { global feiFei
1: '发言',
2: '进入',
3: '送礼',
4: '关注'
}
while __running: while __running:
text = input() text = input()
args = text.split(' ') args = text.split(' ')
@ -177,19 +142,9 @@ def console_listener():
if len(args) == 1: if len(args) == 1:
util.log(1, '错误的参数!') util.log(1, '错误的参数!')
msg = text[3:len(text)] msg = text[3:len(text)]
i = 1 util.printInfo(3, "控制台", '{}: {}'.format('控制台', msg))
try: feiFei.last_quest_time = time.time()
i = int(msg) interact = Interact("console", 1, {'user': '', 'msg': msg})
except:
pass
if i < 1:
i = 1
if i > 4:
i = 4
util.printInfo(1, type_names[i], '{}: {}'.format('控制台', msg))
if i == 1:
feiFei.last_quest_time = time.time()
interact = Interact("console", i, {'user': '', 'msg': msg})
thr = MyThread(target=feiFei.on_interact, args=[interact]) thr = MyThread(target=feiFei.on_interact, args=[interact])
thr.start() thr.start()
thr.join() thr.join()
@ -200,16 +155,12 @@ def console_listener():
#停止服务 #停止服务
def stop(): def stop():
global feiFei global feiFei
global viewerListener
global recorderListener global recorderListener
global __running global __running
global deviceInputListener global deviceInputListener
util.log(1, '正在关闭服务...') util.log(1, '正在关闭服务...')
__running = False __running = False
if viewerListener is not None:
util.log(1, '正在关闭直播服务...')
viewerListener.stop()
if recorderListener is not None: if recorderListener is not None:
util.log(1, '正在关闭录音服务...') util.log(1, '正在关闭录音服务...')
recorderListener.stop() recorderListener.stop()
@ -222,26 +173,21 @@ def stop():
def start(): def start():
# global ws_server
global feiFei global feiFei
global viewerListener
global recorderListener global recorderListener
global __running global __running
global deviceInputListener global deviceInputListener
util.log(1, '开启服务...') util.log(1, '开启服务...')
__running = True __running = True
util.log(1, '读取配置...') util.log(1, '读取配置...')
config_util.load_config() config_util.load_config()
util.log(1, '开启核心服务...') util.log(1, '开启核心服务...')
feiFei = FeiFei() feiFei = FeiFei()
feiFei.start() feiFei.start()
liveRoom = config_util.config['source']['liveRoom']
record = config_util.config['source']['record'] record = config_util.config['source']['record']
if record['enabled']: if record['enabled']:
@ -265,6 +211,5 @@ def start():
if __name__ == '__main__': if __name__ == '__main__':
ws_server: MyServer = None ws_server: MyServer = None
feiFei: FeiFei = None feiFei: FeiFei = None
viewerListener: Viewer = None
recorderListener: Recorder = None recorderListener: Recorder = None
start() start()

View File

@ -42,6 +42,14 @@ def api_submit():
data = request.values.get('data') data = request.values.get('data')
# print(data) # print(data)
config_data = json.loads(data) config_data = json.loads(data)
if(config_data['config']['source']['record']['enabled']):
config_data['config']['source']['record']['channels'] = 0
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
devInfo = audio.get_device_info_by_index(i)
if devInfo['name'].find(config_data['config']['source']['record']['device']) >= 0 and devInfo['hostApi'] == 0:
config_data['config']['source']['record']['channels'] = devInfo['maxInputChannels']
config_util.save_config(config_data['config']) config_util.save_config(config_data['config'])

View File

@ -540,6 +540,11 @@ new Vue({
//滚动条置底 //滚动条置底
let height = document.querySelector('.content').scrollHeight; let height = document.querySelector('.content').scrollHeight;
document.querySelector(".content").scrollTop = height; document.querySelector(".content").scrollTop = height;
this.timer = setTimeout(()=>{ //设置延迟执行
//滚动条置底
let height = document.querySelector('.content').scrollHeight;
document.querySelector(".content").scrollTop = height;
},1000)
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);

View File

@ -20,8 +20,7 @@
<title>Fay</title> <title>Fay</title>
<style> <style>
.container { .container {
max-height: calc(100vh - 55px); height: 953px;
min-height: 953px;
width: 913px; width: 913px;
border-radius: 4px; border-radius: 4px;
border: 0.5px solid #e0e0e0; border: 0.5px solid #e0e0e0;
@ -240,7 +239,15 @@
</div> </div>
<div class="character_right"> <div class="character_right">
<ul> <ul>
<li>
<p>声音选择:{{attribute_voice}}</p>
<el-select v-model="attribute_voice" placeholder="请选择">
<el-option v-for="item in voice_list" :key="item.value"
:label="item.label" :value="item.value">
</el-option>
</el-select>
</li>
<br>
<li> <li>
<p>敏感度:</p> <p>敏感度:</p>
<el-slider v-model="interact_perception_follow"></el-slider> <el-slider v-model="interact_perception_follow"></el-slider>
@ -263,10 +270,7 @@
<div class="character_box"> <div class="character_box">
<p>Q&A文件</p> <p>Q&A文件</p>
<el-input v-model="interact_QnA" placeholder="请输入内容"></el-input> <el-input v-model="interact_QnA" placeholder="请输入内容"></el-input>
<!-- <el-upload class="upload-demo" action="http://127.0.0.1:5000/"-->
<!-- :on-success="handlePreview">-->
<!-- <el-input v-model="interact_QnA" placeholder="请输入内容"></el-input>-->
<!-- </el-upload>-->
</div> </div>
</div> </div>
</div> </div>
@ -350,31 +354,10 @@
</div> </div>
</div> </div>
</div> </div>
<script>
// function send() {
// let text = document.querySelector('#textarea').value;
// if (!text) {
// alert('请输入内容');
// return;
// }
// // text = text.replace(/\s/g, "<br/>");
// text = text.replace(/\n/g, "<br/>");
// text = text.replace(/\r\n/g, "<br/>");
// let item = document.createElement('div');
// item.className = 'item item-right';
// item.innerHTML = `<div class="bubble bubble-right">${text}</div><div class="avatar"><img src="{{ url_for('static',filename='from.jpg') }}" /></div>`;
// document.querySelector('.content').appendChild(item);
// document.querySelector('#textarea').value = '';
// document.querySelector('#textarea').focus();
// //滚动条置底
// let height = document.querySelector('.content').scrollHeight;
// document.querySelector(".content").scrollTop = height;
// }
</script>
</body> </body>
<!-- 开发环境vue.js --> <!-- 开发环境vue.js -->
<script src="{{ url_for('static',filename='js/vue.js') }}"></script> <script src="{{ url_for('static',filename='js/vue.js') }}"></script>
<!--<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>-->
<!-- 发行环境vue.js --> <!-- 发行环境vue.js -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> --> <!-- <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> -->
<!-- 引入element-ui组件库 --> <!-- 引入element-ui组件库 -->

View File

@ -12,6 +12,8 @@ from gui.window import MainWindow
from utils import config_util from utils import config_util
from scheduler.thread_manager import MyThread from scheduler.thread_manager import MyThread
from core.content_db import Content_Db from core.content_db import Content_Db
import sys
sys.setrecursionlimit(sys.getrecursionlimit() * 5)
def __clear_samples(): def __clear_samples():
if not os.path.exists("./samples"): if not os.path.exists("./samples"):
@ -29,8 +31,6 @@ def __clear_songs():
os.remove('./songs/' + file_name) os.remove('./songs/' + file_name)
if __name__ == '__main__': if __name__ == '__main__':
__clear_samples() __clear_samples()
__clear_songs() __clear_songs()