diff --git a/config.json b/config.json index f331257e..311502aa 100644 --- a/config.json +++ b/config.json @@ -118,7 +118,26 @@ "idle_time_task_forget_duration": 0.1, "idle_time_task_forget_reserve_num": 1, "image_recognition_schedule_forget_duration": 0.1, - "image_recognition_schedule_forget_reserve_num": 1 + "image_recognition_schedule_forget_reserve_num": 1, + "message_queue_max_len": 10, + "priority_mapping": { + "copywriting": 1, + "abnormal_alarm": 1, + "trends_copywriting": 10, + "schedule": 10, + "idle_time_task": 10, + "image_recognition_schedule": 10, + "local_qa_audio": 20, + "comment": 20, + "song": 20, + "read_comment": 20, + "gift": 20, + "follow": 20, + "key_mapping": 20, + "integral": 20, + "reread": 30, + "reread_top_priority": 999 + } }, "thanks": { "entrance_enable": true, @@ -595,8 +614,9 @@ }, "gpt_sovits": { "type": "api", - "ws_ip_port": "ws://localhost:9872/queue/join", + "gradio_ip_port": "http://127.0.0.1:7860", "api_ip_port": "http://127.0.0.1:9880", + "ws_ip_port": "ws://localhost:9872/queue/join", "ref_audio_path": "F:\\GPT-SoVITS\\raws\\ikaros\\21.wav", "prompt_text": "マスター、どうりょくろか、いいえ、なんでもありません", "prompt_language": "日文", @@ -835,6 +855,7 @@ "enable": false, "idle_time_min": 60, "idle_time_max": 120, + "trigger_type": ["comment"], "copywriting": { "enable": false, "random": true, @@ -1438,7 +1459,6 @@ "comment", "local_qa_audio", "reread", - "direct_reply", "gift", "entrance", "follow", diff --git a/config.json.bak b/config.json.bak index f331257e..311502aa 100644 --- a/config.json.bak +++ b/config.json.bak @@ -118,7 +118,26 @@ "idle_time_task_forget_duration": 0.1, "idle_time_task_forget_reserve_num": 1, "image_recognition_schedule_forget_duration": 0.1, - "image_recognition_schedule_forget_reserve_num": 1 + "image_recognition_schedule_forget_reserve_num": 1, + "message_queue_max_len": 10, + "priority_mapping": { + "copywriting": 1, + "abnormal_alarm": 1, + "trends_copywriting": 10, + "schedule": 10, + "idle_time_task": 10, + "image_recognition_schedule": 10, + "local_qa_audio": 20, + "comment": 20, + "song": 20, + "read_comment": 20, + "gift": 20, + "follow": 20, + "key_mapping": 20, + "integral": 20, + "reread": 30, + "reread_top_priority": 999 + } }, "thanks": { "entrance_enable": true, @@ -595,8 +614,9 @@ }, "gpt_sovits": { "type": "api", - "ws_ip_port": "ws://localhost:9872/queue/join", + "gradio_ip_port": "http://127.0.0.1:7860", "api_ip_port": "http://127.0.0.1:9880", + "ws_ip_port": "ws://localhost:9872/queue/join", "ref_audio_path": "F:\\GPT-SoVITS\\raws\\ikaros\\21.wav", "prompt_text": "マスター、どうりょくろか、いいえ、なんでもありません", "prompt_language": "日文", @@ -835,6 +855,7 @@ "enable": false, "idle_time_min": 60, "idle_time_max": 120, + "trigger_type": ["comment"], "copywriting": { "enable": false, "random": true, @@ -1438,7 +1459,6 @@ "comment", "local_qa_audio", "reread", - "direct_reply", "gift", "entrance", "follow", diff --git a/main.py b/main.py index c65a88a0..35ebf27f 100644 --- a/main.py +++ b/main.py @@ -944,6 +944,27 @@ def load_data_list(type): # 创建闲时任务子线程并启动 threading.Thread(target=lambda: asyncio.run(idle_time_task())).start() + # 闲时任务计时自动清零 + def idle_time_auto_clear(type: str): + """闲时任务计时自动清零 + + Args: + type (str): 消息类型(comment/gift/entrance等) + + Returns: + bool: 是否清零的结果 + """ + global config, global_idle_time + + # 触发的类型列表 + type_list = config.get("idle_time_task", "trigger_type") + if type in type_list: + global_idle_time = 0 + + return True + + return False + # 图像识别 定时任务 def image_recognition_schedule_task(type: str): @@ -1068,10 +1089,9 @@ async def _(event): 处理直播间弹幕事件 :param event: 弹幕事件数据 """ - global global_idle_time # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") content = event["data"]["info"][1] # 获取弹幕内容 username = event["data"]["info"][2][1] # 获取发送弹幕的用户昵称 @@ -1092,6 +1112,7 @@ async def _(event): 处理直播间礼物连击事件 :param event: 礼物连击事件数据 """ + idle_time_auto_clear("gift") gift_name = event["data"]["data"]["gift_name"] username = event["data"]["data"]["uname"] @@ -1119,6 +1140,7 @@ async def _(event): 处理直播间礼物事件 :param event: 礼物事件数据 """ + idle_time_auto_clear("gift") # print(event) @@ -1159,6 +1181,8 @@ async def _(event): 处理直播间醒目留言(SC)事件 :param event: 醒目留言(SC)事件数据 """ + idle_time_auto_clear("gift") + message = event["data"]["data"]["message"] uname = event["data"]["data"]["user_info"]["uname"] price = event["data"]["data"]["price"] @@ -1188,6 +1212,8 @@ async def _(event): """ global last_username_list + idle_time_auto_clear("entrance") + username = event["data"]["data"]["uname"] logging.info(f"用户:{username} 进入直播间") @@ -1362,6 +1388,8 @@ def __interact_word_callback(self, client: blivedm.BLiveClient, command: dict): global last_username_list + idle_time_auto_clear("entrance") + username = command['data']['uname'] logging.info(f"用户:{username} 进入直播间") @@ -1383,10 +1411,8 @@ def _on_heartbeat(self, client: blivedm.BLiveClient, message: web_models.Heartbe logging.debug(f'[{client.room_id}] 心跳') def _on_danmaku(self, client: blivedm.BLiveClient, message: web_models.DanmakuMessage): - global global_idle_time - # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") # logging.info(f'[{client.room_id}] {message.uname}:{message.msg}') content = message.msg # 获取弹幕内容 @@ -1407,7 +1433,8 @@ def _on_danmaku(self, client: blivedm.BLiveClient, message: web_models.DanmakuMe def _on_gift(self, client: blivedm.BLiveClient, message: web_models.GiftMessage): # logging.info(f'[{client.room_id}] {message.uname} 赠送{message.gift_name}x{message.num}' # f' ({message.coin_type}瓜子x{message.total_coin})') - + idle_time_auto_clear("gift") + gift_name = message.gift_name username = message.uname user_face = message.face @@ -1435,6 +1462,7 @@ def _on_buy_guard(self, client: blivedm.BLiveClient, message: web_models.GuardBu def _on_super_chat(self, client: blivedm.BLiveClient, message: web_models.SuperChatMessage): # logging.info(f'[{client.room_id}] 醒目留言 ¥{message.price} {message.uname}:{message.message}') + idle_time_auto_clear("gift") message = message.message uname = message.uname @@ -1463,10 +1491,8 @@ def _on_heartbeat(self, client: blivedm.BLiveClient, message: web_models.Heartbe logging.debug(f'[{client.room_id}] 心跳') def _on_open_live_danmaku(self, client: blivedm.OpenLiveClient, message: open_models.DanmakuMessage): - global global_idle_time - # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") # logging.info(f'[{client.room_id}] {message.uname}:{message.msg}') content = message.msg # 获取弹幕内容 @@ -1487,6 +1513,8 @@ def _on_open_live_danmaku(self, client: blivedm.OpenLiveClient, message: open_mo my_handle.process_data(data, "comment") def _on_open_live_gift(self, client: blivedm.OpenLiveClient, message: open_models.GiftMessage): + idle_time_auto_clear("gift") + gift_name = message.gift_name username = message.uname user_face = message.uface @@ -1516,6 +1544,8 @@ def _on_open_live_buy_guard(self, client: blivedm.OpenLiveClient, message: open_ def _on_open_live_super_chat( self, client: blivedm.OpenLiveClient, message: open_models.SuperChatMessage ): + idle_time_auto_clear("gift") + print(f'[{message.room_id}] 醒目留言 ¥{message.rmb} {message.uname}:{message.message}') message = message.message @@ -1568,7 +1598,7 @@ async def on_message(websocket, path): if data_json["type"] == "comment": # logging.info(data_json) # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") username = data_json["username"] content = data_json["content"] @@ -1617,7 +1647,7 @@ def on_message(ws, message): if type == 1: # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") username = data_json["User"]["Nickname"] content = data_json["Content"] @@ -1641,6 +1671,8 @@ def on_message(ws, message): logging.info(f'[👍直播间点赞消息] {username} 点了{count}赞') elif type == 3: + idle_time_auto_clear("entrance") + username = data_json["User"]["Nickname"] logging.info(f'[🚹🚺直播间成员加入消息] 欢迎 {username} 进入直播间') @@ -1657,6 +1689,8 @@ def on_message(ws, message): my_handle.process_data(data, "entrance") elif type == 4: + idle_time_auto_clear("follow") + username = data_json["User"]["Nickname"] logging.info(f'[➕直播间关注消息] 感谢 {data_json["User"]["Nickname"]} 的关注') @@ -1671,6 +1705,8 @@ def on_message(ws, message): pass elif type == 5: + idle_time_auto_clear("gift") + gift_name = data_json["GiftName"] username = data_json["User"]["Nickname"] # 礼物数量 @@ -1811,7 +1847,7 @@ async def on_message(websocket, path): if data_json["type"] == "comment": # logging.info(data_json) # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") username = data_json["username"] content = data_json["content"] @@ -2004,8 +2040,6 @@ def websocket_close(self): self.browser.close() def handler(self, websocket): - global global_idle_time - Message = kuaishou_pb2.SocketMessage() Message.ParseFromString(websocket) if Message.payloadType == 310: @@ -2019,7 +2053,7 @@ def handler(self, websocket): msg_list = obj.get('commentFeeds', '') for i in msg_list: # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") username = i['user']['userName'] pid = i['user']['principalId'] @@ -2034,6 +2068,8 @@ def handler(self, websocket): my_handle.process_data(data, "comment") if obj.get('giftFeeds', ''): + idle_time_auto_clear("gift") + msg_list = obj.get('giftFeeds', '') for i in msg_list: username = i['user']['userName'] @@ -2118,6 +2154,8 @@ async def on_disconnect(event: DisconnectEvent): @client.on("join") async def on_join(event: JoinEvent): + idle_time_auto_clear("entrance") + username = event.user.nickname unique_id = event.user.unique_id @@ -2138,7 +2176,7 @@ async def on_join(event: JoinEvent): @client.on("comment") async def on_comment(event: CommentEvent): # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") username = event.user.nickname content = event.comment @@ -2164,6 +2202,7 @@ async def on_gift(event: GiftEvent): If the gift type isn't 1, it can't repeat. Therefore, we can go straight to logging.infoing """ + idle_time_auto_clear("gift") # Streakable gift & streak is over if event.gift.streakable and not event.gift.streaking: @@ -2219,6 +2258,8 @@ async def on_gift(event: GiftEvent): @client.on("follow") async def on_follow(event: FollowEvent): + idle_time_auto_clear("follow") + username = event.user.nickname logging.info(f'[➕直播间关注消息] 感谢 {username} 的关注') @@ -2299,7 +2340,7 @@ async def on_follow(event: FollowEvent): elif not user in resp: # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") resp = demojize(resp) @@ -2366,7 +2407,7 @@ async def on_follow(event: FollowEvent): @app.route('/wxlive', methods=['POST']) def wxlive(): - global my_handle, config, global_idle_time + global my_handle, config try: # 获取 POST 请求中的数据 @@ -2387,7 +2428,7 @@ def wxlive(): # 弹幕数据 if data['events'][0]['decoded_type'] == "comment": # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") content = data['events'][0]['content'] # 获取弹幕内容 username = data['events'][0]['nickname'] # 获取发送弹幕的用户昵称 @@ -2403,6 +2444,8 @@ def wxlive(): my_handle.process_data(data, "comment") # 入场数据 elif data['events'][0]['decoded_type'] == "enter": + idle_time_auto_clear("entrance") + username = data['events'][0]['nickname'] logging.info(f"用户:{username} 进入直播间") @@ -2470,7 +2513,7 @@ def send(): chat_raw = chat_raw.replace('#', '') if chat_raw != '': # 闲时计数清零 - global_idle_time = 0 + idle_time_auto_clear("comment") # chat_author makes the chat look like this: "Nightbot: Hello". So the assistant can respond to the user's name # chat = '[' + c.author.name + ']: ' + chat_raw @@ -2505,7 +2548,7 @@ def send(): # 退出程序 def exit_handler(signum, frame): - print("Received signal:", signum) + print("收到信号:", signum) if __name__ == '__main__': @@ -2528,6 +2571,7 @@ def exit_handler(signum, frame): do_listen_and_comment_thread = None stop_do_listen_and_comment_thread_event = None + # 信号特殊处理 signal.signal(signal.SIGINT, exit_handler) signal.signal(signal.SIGTERM, exit_handler) diff --git a/tests/test_gpt_sovits/gradio0322.py b/tests/test_gpt_sovits/gradio0322.py new file mode 100644 index 00000000..8586f380 --- /dev/null +++ b/tests/test_gpt_sovits/gradio0322.py @@ -0,0 +1,21 @@ +from gradio_client import Client + +client = Client("http://127.0.0.1:9872/") +result = client.predict( + "你好,Hello!!", # str in '需要合成的文本' Textbox component + "中英混合", # Literal['中文', '英文', '日文', '中英混合', '日英混合', '多语种混合'] in '需要合成的语种' Dropdown component + "F:\\GPT-SoVITS\\raws\\ikaros\\21.wav", # filepath in '请上传3~10秒内参考音频,超过会报错!' Audio component + "マスター、どうりょくろか、いいえ、なんでもありません", # str in '参考音频的文本' Textbox component + "日文", # Literal['中文', '英文', '日文', '中英混合', '日英混合', '多语种混合'] in '参考音频的语种' Dropdown component + 1, # float (numeric value between 1 and 100) in 'top_k' Slider component + 0.8, # float (numeric value between 0 and 1) in 'top_p' Slider component + 0.8, # float (numeric value between 0 and 1) in 'temperature' Slider component + "按标点符号切", # Literal['不切', '凑四句一切', '凑50字一切', '按中文句号。切', '按英文句号.切', '按标点符号切'] in '怎么切' Radio component + 20, # float (numeric value between 1 and 200) in 'batch_size' Slider component + 1, # float (numeric value between 0.25 and 4) in 'speed_factor' Slider component + False, # bool in '开启无参考文本模式。不填参考文本亦相当于开启。' Checkbox component + True, # bool in '数据分桶(可能会降低一点计算量,选就对了)' Checkbox component + 0.3, # float (numeric value between 0.01 and 1) in '分段间隔(秒)' Slider component + api_name="/inference" +) +print(result) \ No newline at end of file diff --git a/utils/audio.py b/utils/audio.py index 77ef1c32..2540811a 100644 --- a/utils/audio.py +++ b/utils/audio.py @@ -39,6 +39,10 @@ class Audio: # 创建消息队列 message_queue = Queue() + # 消息列表,存储待合成音频的json数据 + message_queue = [] + message_queue_lock = threading.Lock() + message_queue_not_empty = threading.Condition(lock=message_queue_lock) # 创建音频路径队列 voice_tmp_path_queue = Queue() # # 文案单独一个线程排队播放 @@ -118,7 +122,10 @@ def is_audio_queue_empty(self): flag = 0 # 判断队列是否为空 - if Audio.message_queue.empty(): + # if Audio.message_queue.empty(): + # flag += 1 + + if len(Audio.message_queue): flag += 1 if Audio.voice_tmp_path_queue.empty(): @@ -204,10 +211,19 @@ async def message_queue_thread(self): logging.info("创建音频合成消息队列线程") while True: # 无限循环,直到队列为空时退出 try: - message = Audio.message_queue.get(block=True) + # 获取线程锁,避免同时操作 + with Audio.message_queue_lock: + while not Audio.message_queue: + # 消费者在消费完一个消息后,如果列表为空,则调用wait()方法阻塞自己,直到有新消息到来 + Audio.message_queue_not_empty.wait() # 阻塞直到列表非空 + message = Audio.message_queue.pop(0) logging.debug(message) await self.my_play_voice(message) - Audio.message_queue.task_done() + + # message = Audio.message_queue.get(block=True) + # logging.debug(message) + # await self.my_play_voice(message) + # Audio.message_queue.task_done() # 加个延时 降低点edge-tts的压力 # await asyncio.sleep(0.5) @@ -371,6 +387,73 @@ async def digital_human_video_player_api(self, audio_path=""): logging.error(traceback.format_exc()) return False + # 数据根据优先级排队插入待合成音频队列 + def data_priority_insert(self, audio_json): + """ + 数据根据优先级排队插入待合成音频队列 + + type目前有 + reread_top_priority 最高优先级-复读 + comment 弹幕 + local_qa_audio 本地问答音频 + song 歌曲 + reread 复读 + key_mapping 按键映射 + integral 积分 + read_comment 念弹幕 + gift 礼物 + entrance 用户入场 + follow 用户关注 + schedule 定时任务 + idle_time_task 闲时任务 + abnormal_alarm 异常报警 + image_recognition_schedule 图像识别定时任务 + trends_copywriting 动态文案 + """ + logging.debug(f"message_queue: {Audio.message_queue}") + logging.debug(f"audio_json: {audio_json}") + + # 定义 type 到优先级的映射,相同优先级的 type 映射到相同的值,值越大优先级越高 + priority_mapping = self.config.get("filter", "priority_mapping") + + def get_priority_level(audio_json): + """根据 audio_json 的 'type' 键返回优先级,未定义的 type 或缺失 'type' 键将返回 None""" + # 检查 audio_json 是否包含 'type' 键且该键的值在 priority_mapping 中 + audio_type = audio_json.get("type") + return priority_mapping.get(audio_type, None) + + # 查找插入位置 + new_data_priority = get_priority_level(audio_json) + + logging.debug(f"优先级: {new_data_priority}") + + # 如果新数据没有 'type' 键或其类型不在 priority_mapping 中,直接插入到末尾 + if new_data_priority is None: + insert_position = len(Audio.message_queue) + else: + insert_position = 0 # 默认插入到列表开头 + # 从列表的最后一个元素开始,向前遍历列表,直到第一个元素 + for i in range(len(Audio.message_queue) - 1, -1, -1): + item_priority = get_priority_level(Audio.message_queue[i]) + # 确保比较时排除未定义类型的元素 + if item_priority is not None and item_priority >= new_data_priority: + # 如果找到一个元素,其优先级小于或等于新数据,则将新数据插入到此元素之后 + insert_position = i + 1 + break + + # 数据队列数据量超长判断,插入位置索引大于最大数,则说明优先级低与队列中已存在数据,丢弃数据 + if insert_position >= int(self.config.get("filter", "message_queue_max_len")): + logging.info(f"message_queue 已满,数据丢弃") + return {"code": 1, "msg": f"message_queue 已满,数据丢弃:【{audio_json['content']}】"} + + # 获取线程锁,避免同时操作 + with Audio.message_queue_lock: + # 在计算出的位置插入新数据 + Audio.message_queue.insert(insert_position, audio_json) + # 生产者通过notify()通知消费者列表中有新的消息 + Audio.message_queue_not_empty.notify() + + return {"code": 200, "msg": f"数据已插入到位置 {insert_position}"} # 音频合成(edge-tts / vits_fast)并播放 def audio_synthesis(self, message): @@ -393,7 +476,7 @@ def audio_synthesis(self, message): # 是否开启了音频播放 if self.config.get("play_audio", "enable"): # Audio.voice_tmp_path_queue.put(data_json) - Audio.message_queue.put(data_json) + self.data_priority_insert(data_json) return # 异常报警 elif message['type'] == "abnormal_alarm": @@ -411,7 +494,7 @@ def audio_synthesis(self, message): # 是否开启了音频播放 if self.config.get("play_audio", "enable"): # Audio.voice_tmp_path_queue.put(data_json) - Audio.message_queue.put(data_json) + self.data_priority_insert(data_json) return # 是否为本地问答音频 elif message['type'] == "local_qa_audio": @@ -437,15 +520,15 @@ def audio_synthesis(self, message): logging.info(f"tmp_message={tmp_message}") - Audio.message_queue.put(tmp_message) + self.data_priority_insert(tmp_message) # else: # logging.info(f"message={message}") - # Audio.message_queue.put(message) + # self.data_priority_insert(message) # 是否开启了音频播放 if self.config.get("play_audio", "enable"): # Audio.voice_tmp_path_queue.put(data_json) - Audio.message_queue.put(data_json) + self.data_priority_insert(data_json) return # 是否为助播-本地问答音频 elif message['type'] == "assistant_anchor_audio": @@ -463,7 +546,7 @@ def audio_synthesis(self, message): # 是否开启了音频播放 if self.config.get("play_audio", "enable"): # Audio.voice_tmp_path_queue.put(data_json) - Audio.message_queue.put(data_json) + self.data_priority_insert(data_json) return # 只有信息类型是 弹幕,才会进行念用户名 @@ -477,7 +560,7 @@ def audio_synthesis(self, message): # 将用户名中特殊字符替换为空 message['username'] = self.common.replace_special_characters(message['username'], "!!@#¥$%^&*_-+/——=()()【】}|{:;<>~`\\") tmp_message['content'] = tmp_message['content'].format(username=message['username'][:self.config.get("read_username", "username_max_len")]) - Audio.message_queue.put(tmp_message) + self.data_priority_insert(tmp_message) # 闲时任务 elif message['type'] == "idle_time_task": if message['content_type'] in ["comment", "reread"]: @@ -495,7 +578,7 @@ def audio_synthesis(self, message): data_json["insert_index"] = message["insert_index"] # Audio.voice_tmp_path_queue.put(data_json) - Audio.message_queue.put(data_json) + self.data_priority_insert(data_json) return @@ -507,9 +590,9 @@ def audio_synthesis(self, message): message_copy["content"] = s # 修改副本的 content logging.debug(f"s={s}") if not self.common.is_all_space_and_punct(s): - Audio.message_queue.put(message_copy) # 将副本放入队列中 + self.data_priority_insert(message_copy) # 将副本放入队列中 else: - Audio.message_queue.put(message) + self.data_priority_insert(message) # 单独开线程播放 @@ -788,6 +871,7 @@ async def tts_handle(self, message): data = { "type": message["data"]["type"], + "gradio_ip_port": message["data"]["gradio_ip_port"], "ws_ip_port": message["data"]["ws_ip_port"], "api_ip_port": message["data"]["api_ip_port"], "ref_audio_path": message["data"]["ref_audio_path"], @@ -1625,6 +1709,7 @@ async def audio_synthesis_use_local_config(self, content, audio_synthesis_type=" data = { "type": self.config.get("gpt_sovits", "type"), + "gradio_ip_port": self.config.get("gpt_sovits", "gradio_ip_port"), "ws_ip_port": self.config.get("gpt_sovits", "ws_ip_port"), "api_ip_port": self.config.get("gpt_sovits", "api_ip_port"), "ref_audio_path": self.config.get("gpt_sovits", "ref_audio_path"), diff --git a/utils/audio_handle/my_tts.py b/utils/audio_handle/my_tts.py index 319df7c5..6bd9555e 100644 --- a/utils/audio_handle/my_tts.py +++ b/utils/audio_handle/my_tts.py @@ -675,6 +675,29 @@ async def websocket_client_logic(websocket, data_json): # 调用函数并等待结果 voice_tmp_path = await websocket_client(data) + if voice_tmp_path: + new_file_path = self.common.move_file(voice_tmp_path, os.path.join(self.audio_out_path, 'gpt_sovits_' + self.common.get_bj_time(4)), 'gpt_sovits_' + self.common.get_bj_time(4)) + + return new_file_path + elif data["type"] == "gradio_0322": + client = Client(data["gradio_ip_port"]) + voice_tmp_path = client.predict( + data["content"], # str in '需要合成的文本' Textbox component + data["api_0322"]["text_lang"], # Literal['中文', '英文', '日文', '中英混合', '日英混合', '多语种混合'] in '需要合成的语种' Dropdown component + data["api_0322"]["ref_audio_path"], # filepath in '请上传3~10秒内参考音频,超过会报错!' Audio component + data["api_0322"]["prompt_text"], # str in '参考音频的文本' Textbox component + data["api_0322"]["prompt_lang"], # Literal['中文', '英文', '日文', '中英混合', '日英混合', '多语种混合'] in '参考音频的语种' Dropdown component + data["api_0322"]["top_k"], # float (numeric value between 1 and 100) in 'top_k' Slider component + data["api_0322"]["top_p"], # float (numeric value between 0 and 1) in 'top_p' Slider component + data["api_0322"]["temperature"], # float (numeric value between 0 and 1) in 'temperature' Slider component + data["api_0322"]["text_split_method"], # Literal['不切', '凑四句一切', '凑50字一切', '按中文句号。切', '按英文句号.切', '按标点符号切'] in '怎么切' Radio component + int(data["api_0322"]["batch_size"]), # float (numeric value between 1 and 200) in 'batch_size' Slider component + float(data["api_0322"]["speed_factor"]), # float (numeric value between 0.25 and 4) in 'speed_factor' Slider component + data["api_0322"]["split_bucket"], # bool in '开启无参考文本模式。不填参考文本亦相当于开启。' Checkbox component + data["api_0322"]["return_fragment"], # bool in '数据分桶(可能会降低一点计算量,选就对了)' Checkbox component + data["api_0322"]["fragment_interval"], # float (numeric value between 0.01 and 1) in '分段间隔(秒)' Slider component + api_name="/inference" + ) if voice_tmp_path: new_file_path = self.common.move_file(voice_tmp_path, os.path.join(self.audio_out_path, 'gpt_sovits_' + self.common.get_bj_time(4)), 'gpt_sovits_' + self.common.get_bj_time(4)) diff --git a/utils/my_handle.py b/utils/my_handle.py index e6c71589..3c09478a 100644 --- a/utils/my_handle.py +++ b/utils/my_handle.py @@ -381,7 +381,8 @@ def audio_synthesis_handle(self, data_json): local_qa_audio 本地问答音频 song 歌曲 reread 复读 - direct_reply 直接回复 + key_mapping 按键映射 + integral 积分 read_comment 念弹幕 gift 礼物 entrance 用户入场 @@ -392,6 +393,7 @@ def audio_synthesis_handle(self, data_json): image_recognition_schedule 图像识别定时任务 """ + if "content" in data_json: if data_json['content']: # 替换文本内容中\n为空 @@ -1315,7 +1317,7 @@ def get_copywriting_and_audio_synthesis(sign_num): # 生成回复内容 message = { - "type": "direct_reply", + "type": "integral", "tts_type": My_handle.config.get("audio_synthesis_type"), "data": My_handle.config.get(My_handle.config.get("audio_synthesis_type")), "config": My_handle.config.get("filter"), @@ -1359,7 +1361,7 @@ def get_copywriting_and_audio_synthesis(sign_num): # 获取日期部分(前10个字符),并与当前日期字符串比较 if date_string[:10] == datetime.now().date().strftime("%Y-%m-%d"): message = { - "type": "direct_reply", + "type": "integral", "tts_type": My_handle.config.get("audio_synthesis_type"), "data": My_handle.config.get(My_handle.config.get("audio_synthesis_type")), "config": My_handle.config.get("filter"), @@ -1431,7 +1433,7 @@ def get_copywriting_and_audio_synthesis(total_price): # 生成回复内容 message = { - "type": "direct_reply", + "type": "integral", "tts_type": My_handle.config.get("audio_synthesis_type"), "data": My_handle.config.get(My_handle.config.get("audio_synthesis_type")), "config": My_handle.config.get("filter"), @@ -1523,7 +1525,7 @@ def get_copywriting_and_audio_synthesis(view_num): # 生成回复内容 message = { - "type": "direct_reply", + "type": "integral", "tts_type": My_handle.config.get("audio_synthesis_type"), "data": My_handle.config.get(My_handle.config.get("audio_synthesis_type")), "config": My_handle.config.get("filter"), @@ -1624,7 +1626,7 @@ def get_copywriting_and_audio_synthesis(total_integral): # 生成回复内容 message = { - "type": "direct_reply", + "type": "integral", "tts_type": My_handle.config.get("audio_synthesis_type"), "data": My_handle.config.get(My_handle.config.get("audio_synthesis_type")), "config": My_handle.config.get("filter"), @@ -1686,7 +1688,7 @@ def get_a_copywriting_and_audio_synthesis(key_mapping_config, data): # 音频合成时需要用到的重要数据 message = { - "type": "direct_reply", + "type": "key_mapping", "tts_type": My_handle.config.get("audio_synthesis_type"), "data": My_handle.config.get(My_handle.config.get("audio_synthesis_type")), "config": My_handle.config.get("filter"), diff --git a/webui.py b/webui.py index 89d220eb..970a600e 100644 --- a/webui.py +++ b/webui.py @@ -1347,6 +1347,24 @@ def common_textarea_handle(content): config_data["filter"]["image_recognition_schedule_forget_duration"] = round(float(input_filter_image_recognition_schedule_forget_duration.value), 2) config_data["filter"]["image_recognition_schedule_forget_reserve_num"] = int(input_filter_image_recognition_schedule_forget_reserve_num.value) + # 优先级 + config_data["filter"]["message_queue_max_len"] = input_filter_message_queue_max_len.value + config_data["filter"]["priority_mapping"]["idle_time_task"] = input_filter_priority_mapping_idle_time_task.value + config_data["filter"]["priority_mapping"]["image_recognition_schedule"] = input_filter_priority_mapping_image_recognition_schedule.value + config_data["filter"]["priority_mapping"]["local_qa_audio"] = input_filter_priority_mapping_local_qa_audio.value + config_data["filter"]["priority_mapping"]["comment"] = input_filter_priority_mapping_comment.value + config_data["filter"]["priority_mapping"]["song"] = input_filter_priority_mapping_song.value + config_data["filter"]["priority_mapping"]["read_comment"] = input_filter_priority_mapping_read_comment.value + config_data["filter"]["priority_mapping"]["gift"] = input_filter_priority_mapping_gift.value + config_data["filter"]["priority_mapping"]["follow"] = input_filter_priority_mapping_follow.value + config_data["filter"]["priority_mapping"]["reread"] = input_filter_priority_mapping_reread.value + config_data["filter"]["priority_mapping"]["key_mapping"] = input_filter_priority_mapping_key_mapping.value + config_data["filter"]["priority_mapping"]["integral"] = input_filter_priority_mapping_integral.value + config_data["filter"]["priority_mapping"]["reread_top_priority"] = input_filter_priority_mapping_reread_top_priority.value + config_data["filter"]["priority_mapping"]["copywriting"] = input_filter_priority_mapping_copywriting.value + config_data["filter"]["priority_mapping"]["abnormal_alarm"] = input_filter_priority_mapping_abnormal_alarm.value + config_data["filter"]["priority_mapping"]["trends_copywriting"] = input_filter_priority_mapping_trends_copywriting.value + config_data["filter"]["priority_mapping"]["schedule"] = input_filter_priority_mapping_schedule.value # 答谢 if config.get("webui", "show_card", "common_config", "thanks"): @@ -1406,6 +1424,14 @@ def common_textarea_handle(content): config_data["idle_time_task"]["enable"] = switch_idle_time_task_enable.value config_data["idle_time_task"]["idle_time_min"] = int(input_idle_time_task_idle_time_min.value) config_data["idle_time_task"]["idle_time_max"] = int(input_idle_time_task_idle_time_max.value) + + tmp_arr = [] + for index in range(len(idle_time_task_trigger_type_var)): + if idle_time_task_trigger_type_var[str(index)].value: + tmp_arr.append(common.find_keys_by_value(idle_time_task_trigger_type_mapping, idle_time_task_trigger_type_var[str(index)].text)[0]) + # logging.info(tmp_arr) + config_data["idle_time_task"]["trigger_type"] = tmp_arr + config_data["idle_time_task"]["comment"]["enable"] = switch_idle_time_task_comment_enable.value config_data["idle_time_task"]["comment"]["random"] = switch_idle_time_task_comment_random.value config_data["idle_time_task"]["copywriting"]["copy"] = common_textarea_handle(textarea_idle_time_task_copywriting_copy.value) @@ -1954,6 +1980,7 @@ def common_textarea_handle(content): if config.get("webui", "show_card", "tts", "gpt_sovits"): config_data["gpt_sovits"]["type"] = select_gpt_sovits_type.value + config_data["gpt_sovits"]["gradio_ip_port"] = input_gpt_sovits_gradio_ip_port.value config_data["gpt_sovits"]["api_ip_port"] = input_gpt_sovits_api_ip_port.value config_data["gpt_sovits"]["ws_ip_port"] = input_gpt_sovits_ws_ip_port.value config_data["gpt_sovits"]["ref_audio_path"] = input_gpt_sovits_ref_audio_path.value @@ -2678,7 +2705,34 @@ def common_textarea_handle(content): input_filter_idle_time_task_forget_reserve_num = ui.input(label='闲时任务保留数', placeholder='保留最新收到的数据的数量', value=config.get("filter", "idle_time_task_forget_reserve_num")).style("width:200px;") input_filter_image_recognition_schedule_forget_duration = ui.input(label='图像识别遗忘间隔', placeholder='指的是每隔这个间隔时间(秒),就会丢弃这个间隔时间中接收到的数据,\n保留数据在以下配置中可以自定义', value=config.get("filter", "image_recognition_schedule_forget_duration")).style("width:200px;") input_filter_image_recognition_schedule_forget_reserve_num = ui.input(label='图像识别保留数', placeholder='保留最新收到的数据的数量', value=config.get("filter", "image_recognition_schedule_forget_reserve_num")).style("width:200px;") - + with ui.expansion('待合成音频的消息队列', icon="settings", value=True).classes('w-full'): + with ui.row(): + input_filter_message_queue_max_len = ui.input(label='消息队列最大保留长度', placeholder='收到的消息,生成的文本内容,会根据优先级存入消息队列,当新消息的优先级低于队列中所有的消息且超过此长度时,此消息将被丢弃', value=config.get("filter", "message_queue_max_len")).style("width:200px;") + with ui.element('div').classes('p-2 bg-blue-100'): + ui.label("下方优先级配置,请使用正整数。数字越大,优先级越高,就会优先合成音频播放\n另外需要注意,由于shi山原因,目前这个队列内容是文本切分后计算的长度,所以如果回复内容过长,可能会有丢数据的情况") + with ui.grid(columns=4): + input_filter_priority_mapping_idle_time_task = ui.input(label='闲时任务 优先级', value=config.get("filter", "priority_mapping", "idle_time_task"), placeholder='数字越大,优先级越高,文案页的文案,但这个并非文本,所以暂时没啥用,预留').style("width:200px;") + input_filter_priority_mapping_image_recognition_schedule = ui.input(label='图像识别 优先级', value=config.get("filter", "priority_mapping", "image_recognition_schedule"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_local_qa_audio = ui.input(label='本地问答-音频 优先级', value=config.get("filter", "priority_mapping", "local_qa_audio"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_comment = ui.input(label='弹幕回复 优先级', value=config.get("filter", "priority_mapping", "comment"), placeholder='数字越大,优先级越高').style("width:200px;") + with ui.grid(columns=4): + input_filter_priority_mapping_song = ui.input(label='点歌 优先级', value=config.get("filter", "priority_mapping", "song"), placeholder='数字越大,优先级越高,文案页的文案,但这个并非文本,所以暂时没啥用,预留').style("width:200px;") + input_filter_priority_mapping_read_comment = ui.input(label='念弹幕 优先级', value=config.get("filter", "priority_mapping", "read_comment"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_gift = ui.input(label='礼物答谢 优先级', value=config.get("filter", "priority_mapping", "gift"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_follow = ui.input(label='关注答谢 优先级', value=config.get("filter", "priority_mapping", "follow"), placeholder='数字越大,优先级越高').style("width:200px;") + with ui.grid(columns=4): + input_filter_priority_mapping_reread = ui.input(label='复读 优先级', value=config.get("filter", "priority_mapping", "reread"), placeholder='数字越大,优先级越高,文案页的文案,但这个并非文本,所以暂时没啥用,预留').style("width:200px;") + input_filter_priority_mapping_key_mapping = ui.input(label='按键映射 优先级', value=config.get("filter", "priority_mapping", "key_mapping"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_integral = ui.input(label='积分 优先级', value=config.get("filter", "priority_mapping", "integral"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_reread_top_priority = ui.input(label='最高优先级复读 优先级', value=config.get("filter", "priority_mapping", "reread_top_priority"), placeholder='数字越大,优先级越高').style("width:200px;") + + with ui.grid(columns=4): + input_filter_priority_mapping_copywriting = ui.input(label='文案 优先级', value=config.get("filter", "priority_mapping", "copywriting"), placeholder='数字越大,优先级越高,文案页的文案,但这个并非文本,所以暂时没啥用,预留').style("width:200px;") + input_filter_priority_mapping_abnormal_alarm = ui.input(label='异常报警 优先级', value=config.get("filter", "priority_mapping", "abnormal_alarm"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_trends_copywriting = ui.input(label='动态文案 优先级', value=config.get("filter", "priority_mapping", "trends_copywriting"), placeholder='数字越大,优先级越高').style("width:200px;") + input_filter_priority_mapping_schedule = ui.input(label='定时任务 优先级', value=config.get("filter", "priority_mapping", "schedule"), placeholder='数字越大,优先级越高').style("width:200px;") + + if config.get("webui", "show_card", "common_config", "thanks"): @@ -2738,7 +2792,26 @@ def common_textarea_handle(content): switch_idle_time_task_enable = ui.switch('启用', value=config.get("idle_time_task", "enable")).style(switch_internal_css) input_idle_time_task_idle_time_min = ui.input(label='最小闲时时间', value=config.get("idle_time_task", "idle_time_min"), placeholder='最小闲时间隔时间(正整数,单位:秒),就是在没有弹幕情况下经过的时间').style("width:150px;") input_idle_time_task_idle_time_max = ui.input(label='最大闲时时间', value=config.get("idle_time_task", "idle_time_max"), placeholder='最大闲时间隔时间(正整数,单位:秒),就是在没有弹幕情况下经过的时间').style("width:150px;") + + with ui.row(): + ui.label('刷新闲时计时的消息类型') + # 类型列表 + idle_time_task_trigger_type_list = ["comment", "gift", "entrance", "follow"] + idle_time_task_trigger_type_mapping = { + "comment": "弹幕", + "gift": "礼物", + "entrance": "入场", + "follow": "关注", + } + idle_time_task_trigger_type_var = {} + for index, idle_time_task_trigger_type in enumerate(idle_time_task_trigger_type_list): + if idle_time_task_trigger_type in config.get("idle_time_task", "trigger_type"): + idle_time_task_trigger_type_var[str(index)] = ui.checkbox(text=idle_time_task_trigger_type_mapping[idle_time_task_trigger_type], value=True) + else: + idle_time_task_trigger_type_var[str(index)] = ui.checkbox(text=idle_time_task_trigger_type_mapping[idle_time_task_trigger_type], value=False) + + with ui.row(): switch_idle_time_task_copywriting_enable = ui.switch('文案模式', value=config.get("idle_time_task", "copywriting", "enable")).style(switch_internal_css) switch_idle_time_task_copywriting_random = ui.switch('随机文案', value=config.get("idle_time_task", "copywriting", "random")).style(switch_internal_css) @@ -4119,10 +4192,17 @@ def vits_get_speaker_id(): with ui.row(): select_gpt_sovits_type = ui.select( label='API类型', - options={'gradio':'gradio旧版', 'api':'api', 'api_0322':'api_0322', 'webtts':'WebTTS'}, + options={'gradio':'gradio旧版', 'gradio_0322':'gradio_0322', 'api':'api', 'api_0322':'api_0322', 'webtts':'WebTTS'}, value=config.get("gpt_sovits", "type") ).style("width:100px;") - input_gpt_sovits_ws_ip_port = ui.input(label='WS地址(gradio)', value=config.get("gpt_sovits", "ws_ip_port"), placeholder='启动TTS推理后,ws的接口地址').style("width:200px;") + input_gpt_sovits_gradio_ip_port = ui.input( + label='Gradio API地址', + value=config.get("gpt_sovits", "gradio_ip_port"), + placeholder='官方webui程序启动后gradio监听的地址', + validation={ + '请输入正确格式的URL': lambda value: common.is_url_check(value), + } + ).style("width:200px;") input_gpt_sovits_api_ip_port = ui.input( label='API地址(http)', value=config.get("gpt_sovits", "api_ip_port"), @@ -4131,6 +4211,8 @@ def vits_get_speaker_id(): '请输入正确格式的URL': lambda value: common.is_url_check(value), } ).style("width:200px;") + input_gpt_sovits_ws_ip_port = ui.input(label='WS地址(gradio)', value=config.get("gpt_sovits", "ws_ip_port"), placeholder='启动TTS推理后,ws的接口地址').style("width:200px;") + with ui.row(): input_gpt_sovits_gpt_model_path = ui.input(label='GPT模型路径', value=config.get("gpt_sovits", "gpt_model_path"), placeholder='GPT模型路径,填绝对路径').style("width:300px;") @@ -4166,7 +4248,7 @@ def vits_get_speaker_id(): ).style("width:200px;") with ui.card().style(card_css): - ui.label("api_0322") + ui.label("api_0322 | gradio_0322") with ui.row(): input_gpt_sovits_api_0322_ref_audio_path = ui.input(label='参考音频路径', value=config.get("gpt_sovits", "api_0322", "ref_audio_path"), placeholder='参考音频路径,建议填绝对路径').style("width:300px;") input_gpt_sovits_api_0322_prompt_text = ui.input(label='参考音频的文本', value=config.get("gpt_sovits", "api_0322", "prompt_text"), placeholder='参考音频的文本').style("width:200px;") @@ -4948,14 +5030,14 @@ async def image_recognition_cam_screenshot_and_send(sleep_time: float): ui.label("触发类型") with ui.row(): # 类型列表源自audio_synthesis_handle 音频合成的所支持的type值 - assistant_anchor_type_list = ["comment", "local_qa_audio", "song", "reread", "direct_reply", "read_comment", "gift", - "entrance", "follow", "idle_time_task", "reread_top_priority", "schedule", "image_recognition_schedule"] + assistant_anchor_type_list = ["comment", "local_qa_audio", "song", "reread", "read_comment", "gift", + "entrance", "follow", "idle_time_task", "reread_top_priority", "schedule", + "image_recognition_schedule", "key_mapping", "integral"] assistant_anchor_type_mapping = { "comment": "弹幕", "local_qa_audio": "本地问答-音频", "song": "点歌", "reread": "复读", - "direct_reply": "直接合成回复", "read_comment": "念弹幕", "gift": "礼物", "entrance": "入场", @@ -4963,7 +5045,9 @@ async def image_recognition_cam_screenshot_and_send(sleep_time: float): "idle_time_task": "闲时任务", "reread_top_priority": "最高优先级-复读", "schedule": "定时任务", - "image_recognition_schedule": "图像识别定时任务" + "image_recognition_schedule": "图像识别定时任务", + "key_mapping": "按键映射", + "integral": "积分", } assistant_anchor_type_var = {}