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]
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}
user_input = user_input.lower()
matches = difflib.get_close_matches(
user_input, lower_collection.keys(), n=1, cutoff=cutoff
)
return lower_collection[matches[0]] if matches else None
matches, remains = keyword_detection(user_input, lower_collection.keys(), n=n)
if len(matches) < n:
# 如果没有准确关键词匹配,开始模糊匹配
matches += difflib.get_close_matches(
user_input, lower_collection.keys(), n=n, cutoff=cutoff
)
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:
def __init__(self, config: Config):
self.config = config
@ -667,20 +674,27 @@ class XiaoMusic:
self.log.info(f"未匹配到指令 {query} {ctrl_panel}")
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:
self.log.debug("没开启模糊匹配")
return name
all_music_list = list(self.all_music.keys())
real_name = find_best_match(
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff
real_names = find_best_match(
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n,
)
if real_name:
self.log.info(f"根据【{name}】找到歌曲【{real_name}")
return real_name
if real_names:
if n > 1:
# 扩大范围再找,最后保留随机 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}")
return name
return []
def did_exist(self, did):
return did in self.devices
@ -728,7 +742,7 @@ class XiaoMusic:
# 模糊搜一个播放列表
real_name = find_best_match(
list_name, self.music_list, cutoff=self.config.fuzzy_match_cutoff
)
)[0]
if real_name:
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}")
list_name = real_name
@ -1006,6 +1020,8 @@ class XiaoMusicDevice:
self._duration = 0
self._paused_time = 0
self._play_list = []
# 关机定时器
self._stop_timer = None
self._last_cmd = None
@ -1023,23 +1039,29 @@ class XiaoMusicDevice:
# 初始化播放列表
def update_playlist(self):
if self.device.cur_playlist not in self.xiaomusic.music_list:
self.device.cur_playlist = "全部"
# 没有重置 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:
self.device.cur_playlist = "全部"
list_name = self.device.cur_playlist
self._play_list = copy.copy(self.xiaomusic.music_list[list_name])
list_name = self.device.cur_playlist
self._play_list = copy.copy(self.xiaomusic.music_list[list_name])
if self.device.play_type == PLAY_TYPE_RND:
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:
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=""):
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 self.check_play_next():
await self._play_next()
@ -1049,8 +1071,20 @@ class XiaoMusicDevice:
self.log.info(f"play. search_key:{search_key} name:{name}")
# 本地歌曲不存在时下载
name = self.xiaomusic.find_real_music_name(name)
if not self.xiaomusic.is_music_exist(name):
if exact:
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:
await self.do_tts(f"本地不存在歌曲{name}")
return
@ -1081,7 +1115,7 @@ class XiaoMusicDevice:
if name == "":
await self.do_tts("本地没有歌曲")
return
await self._play(name)
await self._play(name, exact=True)
# 上一首
async def play_prev(self):
@ -1101,7 +1135,7 @@ class XiaoMusicDevice:
if name == "":
await self.do_tts("本地没有歌曲")
return
await self._play(name)
await self._play(name, exact=True)
# 播放本地歌曲
async def playlocal(self, name):
@ -1116,8 +1150,17 @@ class XiaoMusicDevice:
self.log.info(f"playlocal. name:{name}")
# 本地歌曲不存在时下载
name = self.xiaomusic.find_real_music_name(name)
if not self.xiaomusic.is_music_exist(name):
names = self.xiaomusic.find_real_music_name(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}")
return
await self._playmusic(name)
@ -1464,7 +1507,7 @@ class XiaoMusicDevice:
self.device.cur_playlist = list_name
self.update_playlist()
self.log.info(f"开始播放列表{list_name}")
await self._play(music_name)
await self._play(music_name, exact=True)
async def stop(self, arg1=""):
self._last_cmd = "stop"