feat: 搜索多个结果,并更新“当前”播放列表 (#185)

* local search returns multiple match, and refill play list

* bug fix

* bug fix in play: sometimes we do not need to update play_list

* bug fix in find_best_match; do not update play_list when only 1 match
This commit is contained in:
Gao, Ruiyuan 2024-09-21 21:28:00 +08:00 committed by GitHub
parent 425214d453
commit c72a619df0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 92 additions and 28 deletions

View File

@ -94,13 +94,34 @@ def fuzzyfinder(user_input, collection):
return [lower_collection[match] for match in matches] return [lower_collection[match] for match in matches]
def find_best_match(user_input, collection, cutoff=0.6): # 关键词检测
def keyword_detection(user_input, str_list, n):
# 过滤包含关键字的字符串
matched, remains = [], []
for item in str_list:
if user_input in item:
matched.append(item)
else:
remains.append(item)
# 如果 n 是 -1如果 n 大于匹配的数量,返回所有匹配的结果
if n == -1 or n > len(matched):
return matched, remains
# 随机选择 n 个匹配的结果
return random.sample(matched, n), remains
def find_best_match(user_input, collection, cutoff=0.6, n=1):
lower_collection = {item.lower(): item for item in collection} lower_collection = {item.lower(): item for item in collection}
user_input = user_input.lower() user_input = user_input.lower()
matches = difflib.get_close_matches( matches, remains = keyword_detection(user_input, lower_collection.keys(), n=n)
user_input, lower_collection.keys(), n=1, cutoff=cutoff if len(matches) < n:
# 如果没有准确关键词匹配,开始模糊匹配
matches += difflib.get_close_matches(
user_input, lower_collection.keys(), n=n, cutoff=cutoff
) )
return lower_collection[matches[0]] if matches else None return [lower_collection[match] for match in matches[:n]]
# 歌曲排序 # 歌曲排序

View File

@ -52,6 +52,13 @@ from xiaomusic.utils import (
) )
def list2str(li, verbose=False):
if len(li) > 5 and not verbose:
return f"{li[:2]} ... {li[-2:]} with len: {len(li)}"
else:
return f"{li}"
class XiaoMusic: class XiaoMusic:
def __init__(self, config: Config): def __init__(self, config: Config):
self.config = config self.config = config
@ -667,20 +674,27 @@ class XiaoMusic:
self.log.info(f"未匹配到指令 {query} {ctrl_panel}") self.log.info(f"未匹配到指令 {query} {ctrl_panel}")
return (None, None) return (None, None)
def find_real_music_name(self, name): def find_real_music_name(self, name, n=100):
if not self.config.enable_fuzzy_match: if not self.config.enable_fuzzy_match:
self.log.debug("没开启模糊匹配") self.log.debug("没开启模糊匹配")
return name return name
all_music_list = list(self.all_music.keys()) all_music_list = list(self.all_music.keys())
real_name = find_best_match( real_names = find_best_match(
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n,
) )
if real_name: if real_names:
self.log.info(f"根据【{name}】找到歌曲【{real_name}") if n > 1:
return real_name # 扩大范围再找,最后保留随机 n 个
real_names = find_best_match(
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n * 2,
)
random.shuffle(real_names)
real_names = real_names[:n]
self.log.info(f"根据【{name}】找到歌曲【{real_names}")
return real_names
self.log.info(f"没找到歌曲【{name}") self.log.info(f"没找到歌曲【{name}")
return name return []
def did_exist(self, did): def did_exist(self, did):
return did in self.devices return did in self.devices
@ -728,7 +742,7 @@ class XiaoMusic:
# 模糊搜一个播放列表 # 模糊搜一个播放列表
real_name = find_best_match( real_name = find_best_match(
list_name, self.music_list, cutoff=self.config.fuzzy_match_cutoff list_name, self.music_list, cutoff=self.config.fuzzy_match_cutoff
) )[0]
if real_name: if real_name:
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}") self.log.info(f"根据【{list_name}】找到播放列表【{real_name}")
list_name = real_name list_name = real_name
@ -1006,6 +1020,8 @@ class XiaoMusicDevice:
self._duration = 0 self._duration = 0
self._paused_time = 0 self._paused_time = 0
self._play_list = []
# 关机定时器 # 关机定时器
self._stop_timer = None self._stop_timer = None
self._last_cmd = None self._last_cmd = None
@ -1023,23 +1039,29 @@ class XiaoMusicDevice:
# 初始化播放列表 # 初始化播放列表
def update_playlist(self): def update_playlist(self):
# 没有重置 list 且非初始化
if self.device.cur_playlist == "当前" and len(self._play_list) > 0:
list_name = "当前"
else: # 调用了播放列表功能cur_playlist 为新列表名称
if self.device.cur_playlist not in self.xiaomusic.music_list: if self.device.cur_playlist not in self.xiaomusic.music_list:
self.device.cur_playlist = "全部" self.device.cur_playlist = "全部"
list_name = self.device.cur_playlist list_name = self.device.cur_playlist
self._play_list = copy.copy(self.xiaomusic.music_list[list_name]) self._play_list = copy.copy(self.xiaomusic.music_list[list_name])
if self.device.play_type == PLAY_TYPE_RND: if self.device.play_type == PLAY_TYPE_RND:
random.shuffle(self._play_list) random.shuffle(self._play_list)
self.log.info(f"随机打乱 {list_name} {self._play_list}") self.log.info(f"随机打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
else: else:
self.log.info(f"没打乱 {list_name} {self._play_list}") self._play_list = sorted(self._play_list)
self.log.info(f"没打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
# 播放歌曲 # 播放歌曲
async def play(self, name="", search_key=""): async def play(self, name="", search_key=""):
self._last_cmd = "play" self._last_cmd = "play"
return await self._play(name=name, search_key=search_key) return await self._play(name=name, search_key=search_key, update_cur=True)
async def _play(self, name="", search_key=""): async def _play(self, name="", search_key="", exact=False, update_cur=False):
if search_key == "" and name == "": if search_key == "" and name == "":
if self.check_play_next(): if self.check_play_next():
await self._play_next() await self._play_next()
@ -1049,8 +1071,20 @@ class XiaoMusicDevice:
self.log.info(f"play. search_key:{search_key} name:{name}") self.log.info(f"play. search_key:{search_key} name:{name}")
# 本地歌曲不存在时下载 # 本地歌曲不存在时下载
name = self.xiaomusic.find_real_music_name(name) if exact:
if not self.xiaomusic.is_music_exist(name): names = self.xiaomusic.find_real_music_name(name, n=1)
else:
names = self.xiaomusic.find_real_music_name(name)
if len(names) > 0:
if update_cur and len(names) > 1: # 大于一首歌才更新
self._play_list = names
self.device.cur_playlist = "当前"
self.update_playlist()
elif update_cur: # 只有一首歌append
self._play_list = self._play_list + names
name = names[0]
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
elif not self.xiaomusic.is_music_exist(name):
if self.config.disable_download: if self.config.disable_download:
await self.do_tts(f"本地不存在歌曲{name}") await self.do_tts(f"本地不存在歌曲{name}")
return return
@ -1081,7 +1115,7 @@ class XiaoMusicDevice:
if name == "": if name == "":
await self.do_tts("本地没有歌曲") await self.do_tts("本地没有歌曲")
return return
await self._play(name) await self._play(name, exact=True)
# 上一首 # 上一首
async def play_prev(self): async def play_prev(self):
@ -1101,7 +1135,7 @@ class XiaoMusicDevice:
if name == "": if name == "":
await self.do_tts("本地没有歌曲") await self.do_tts("本地没有歌曲")
return return
await self._play(name) await self._play(name, exact=True)
# 播放本地歌曲 # 播放本地歌曲
async def playlocal(self, name): async def playlocal(self, name):
@ -1116,8 +1150,17 @@ class XiaoMusicDevice:
self.log.info(f"playlocal. name:{name}") self.log.info(f"playlocal. name:{name}")
# 本地歌曲不存在时下载 # 本地歌曲不存在时下载
name = self.xiaomusic.find_real_music_name(name) names = self.xiaomusic.find_real_music_name(name)
if not self.xiaomusic.is_music_exist(name): if len(names) > 0:
if len(names) > 1: # 大于一首歌才更新
self._play_list = names
self.device.cur_playlist = "当前"
self.update_playlist()
else: # 只有一首歌append
self._play_list = self._play_list + names
name = names[0]
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
elif not self.xiaomusic.is_music_exist(name):
await self.do_tts(f"本地不存在歌曲{name}") await self.do_tts(f"本地不存在歌曲{name}")
return return
await self._playmusic(name) await self._playmusic(name)
@ -1464,7 +1507,7 @@ class XiaoMusicDevice:
self.device.cur_playlist = list_name self.device.cur_playlist = list_name
self.update_playlist() self.update_playlist()
self.log.info(f"开始播放列表{list_name}") self.log.info(f"开始播放列表{list_name}")
await self._play(music_name) await self._play(music_name, exact=True)
async def stop(self, arg1=""): async def stop(self, arg1=""):
self._last_cmd = "stop" self._last_cmd = "stop"