Skip to content

Commit

Permalink
feat: TTS合成后优先进入播放(#208); 修复某些特定情况下删除提醒失败的bug
Browse files Browse the repository at this point in the history
  • Loading branch information
wzpan committed Mar 26, 2023
1 parent fee700c commit 1eaaea3
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 57 deletions.
1 change: 1 addition & 0 deletions plugins/Reminder.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def list_reminder(self, parsed):
index = 0
for job in _jobs:
self.say(f"第{index+1}个提醒内容是{job.describe}")
logger.info(f"index: {index}, job.job_id: {job.job_id}")
index += 1
elif len(_jobs) == 1:
self.say(f"您当前有1个提醒。", cache=True)
Expand Down
113 changes: 66 additions & 47 deletions robot/Conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,35 @@ def __init__(self, profiling=False):
self.player = Player.SoxPlayer()
self.lifeCycleHandler = LifeCycleHandler(self)
self.audios = []
self.tts_index = 0
self.tts_lock = threading.Lock()
self.play_lock = threading.Lock()

def _ttsAction(self, msg, index):
def _ttsAction(self, msg, index, cache):
if msg:
logger.info(f"开始合成TTS{msg}")
logger.info(f"开始合成第{index}段TTS{msg}")
voice = ""
if utils.getCache(msg):
logger.info("命中缓存,播放缓存语音")
logger.info(f"第{index}段TTS命中缓存,播放缓存语音")
voice = utils.getCache(msg)
while index != self.tts_index:
# 阻塞直到轮到这个音频播放
continue
with self.play_lock:
self.player.play(voice, not cache)
self.tts_index += 1
return (voice, index)
else:
try:
voice = self.tts.get_speech(msg)
logger.info(f"合成第{index}段TTS合成成功:{msg}")
logger.debug(f"self.tts_index: {self.tts_index}")
while index != self.tts_index:
# 阻塞直到轮到这个音频播放
continue
with self.play_lock:
self.player.play(voice, not cache)
self.tts_index += 1
return (voice, index)
except Exception as e:
logger.error(f"语音合成失败:{e}", stack_info=True)
Expand Down Expand Up @@ -255,50 +272,52 @@ def say(
:param onCompleted: 完成的回调
:param append_history: 是否要追加到聊天记录
"""
append_history and self.appendHistory(1, msg, plugin=plugin)
pattern = r"http[s]?://.+"
if re.match(pattern, msg):
logger.info("内容包含URL,屏蔽后续内容")
msg = re.sub(pattern, "", msg)
msg = utils.stripPunctuation(msg)
msg = msg.strip()
if not msg:
return
logger.info(f"即将朗读语音:{msg}")
# 分拆成多行,分别进行TTS
lines = re.split("。|!|?|\.|\!|\?|\n", msg)
self.audios = []
cached_audios = []
# 创建一个包含5条线程的线程池
with ThreadPoolExecutor(max_workers=5) as pool:
index = 0
all_task = []
for line in lines:
task = pool.submit(self._ttsAction, line, index)
index += 1
all_task.append(task)
res_audios = []
for task in as_completed(all_task):
task.result() and res_audios.append(task.result())
sorted_audios = sorted(res_audios, key=lambda x: x[1])
self.audios = [audio[0] for audio in sorted_audios]
if self.onSay:
for voice in self.audios:
audio = "http://{}:{}/audio/{}".format(
config.get("/server/host"),
config.get("/server/port"),
os.path.basename(voice),
)
cached_audios.append(audio)
logger.info(f"onSay: {msg}, {cached_audios}")
self.onSay(msg, cached_audios, plugin=plugin)
self.onSay = None
for voice in self.audios:
self.player.play(voice, not cache)
if onCompleted is None:
onCompleted = lambda: self._onCompleted(msg)
onCompleted and onCompleted()
utils.lruCache() # 清理缓存
# 确保同时只有一个say
with self.tts_lock:
append_history and self.appendHistory(1, msg, plugin=plugin)
pattern = r"http[s]?://.+"
if re.match(pattern, msg):
logger.info("内容包含URL,屏蔽后续内容")
msg = re.sub(pattern, "", msg)
msg = utils.stripPunctuation(msg)
msg = msg.strip()
if not msg:
return
logger.info(f"即将朗读语音:{msg}")
# 分拆成多行,分别进行TTS
lines = re.split("。|!|?|\.|\!|\?|\n", msg)
self.audios = []
cached_audios = []
self.tts_index = 0
# 创建一个包含5条线程的线程池
with ThreadPoolExecutor(max_workers=5) as pool:
index = 0
all_task = []
for line in lines:
if line:
task = pool.submit(self._ttsAction, line, index, cache)
index += 1
all_task.append(task)
res_audios = []
for task in as_completed(all_task):
task.result() and res_audios.append(task.result())
sorted_audios = sorted(res_audios, key=lambda x: x[1])
self.audios = [audio[0] for audio in sorted_audios]
if self.onSay:
for voice in self.audios:
audio = "http://{}:{}/audio/{}".format(
config.get("/server/host"),
config.get("/server/port"),
os.path.basename(voice),
)
cached_audios.append(audio)
logger.info(f"onSay: {msg}, {cached_audios}")
self.onSay(msg, cached_audios, plugin=plugin)
self.onSay = None
if onCompleted is None:
onCompleted = lambda: self._onCompleted(msg)
onCompleted and onCompleted()
utils.lruCache() # 清理缓存

def activeListen(self, silent=False):
"""
Expand Down
3 changes: 2 additions & 1 deletion robot/Scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ def del_job_by_id(self, job_id):
:param job_id: 提醒id
"""
try:
self._sched.remove_job(job_id=job_id)
if (self._sched.get_job(job_id=job_id)):
self._sched.remove_job(job_id=job_id)
self._jobs = [job for job in self._jobs if job.job_id != job_id]
except Exception as e:
logger.warning(f"id {job_id} 的提醒已被删除。删除失败。")
12 changes: 3 additions & 9 deletions robot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,12 @@ def getTimemStap():
def getCache(msg):
"""获取缓存的语音"""
md5 = hashlib.md5(msg.encode("utf-8")).hexdigest()
mp3_cache = os.path.join(constants.TEMP_PATH, md5 + ".mp3")
wav_cache = os.path.join(constants.TEMP_PATH, md5 + ".wav")
if os.path.exists(mp3_cache):
return mp3_cache
elif os.path.exists(wav_cache):
return wav_cache
return None

cache_paths = [os.path.join(constants.TEMP_PATH, md5 + ext) for ext in ['.mp3', '.wav', '.asiff']]
return next((path for path in cache_paths if os.path.exists(path)), None)

def saveCache(voice, msg):
"""获取缓存的语音"""
foo, ext = os.path.splitext(voice)
_, ext = os.path.splitext(voice)
md5 = hashlib.md5(msg.encode("utf-8")).hexdigest()
target = os.path.join(constants.TEMP_PATH, md5 + ext)
shutil.copyfile(voice, target)
Expand Down

0 comments on commit 1eaaea3

Please sign in to comment.