feat: 搜索的歌曲存成列表供前端显示,实现额外索引 (#188)

* bug fix after cd54dae; implement #186

* bug fix

* convert musiclist to basename

* revert to basename in all_music

* move list2str to utils.py

* use _extra_index_search in search
This commit is contained in:
Gao, Ruiyuan 2024-09-22 21:22:52 +08:00 committed by GitHub
parent c1915fb6b1
commit a428f377d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 86 additions and 51 deletions

View File

@ -85,13 +85,9 @@ def validate_proxy(proxy_str: str) -> bool:
# 模糊搜索
def fuzzyfinder(user_input, collection):
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=10, cutoff=0.1
)
return [lower_collection[match] for match in matches]
def fuzzyfinder(user_input, collection, extra_search_index=None):
return find_best_match(user_input, collection, cutoff=0.1, n=10,
extra_search_index=extra_search_index)
# 关键词检测
@ -112,18 +108,31 @@ def keyword_detection(user_input, str_list, 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, remains = keyword_detection(user_input, lower_collection.keys(), n=n)
def real_search(prompt, candidates, cutoff, n):
matches, remains = keyword_detection(prompt, candidates, n=n)
if len(matches) < n:
# 如果没有准确关键词匹配,开始模糊匹配
matches += difflib.get_close_matches(
user_input, lower_collection.keys(), n=n, cutoff=cutoff
prompt, remains, n=n, cutoff=cutoff
)
return [lower_collection[match] for match in matches[:n]]
return matches
def find_best_match(user_input, collection, cutoff=0.6, n=1, extra_search_index=None):
lower_collection = {item.lower(): item for item in collection}
user_input = user_input.lower()
matches = real_search(user_input, lower_collection.keys(), cutoff, n)
cur_matched_collection = [lower_collection[match] for match in matches]
if len(matches) >= n or extra_search_index is None:
return cur_matched_collection[:n]
# 如果数量不满足,继续搜索
lower_extra_search_index = {k.lower():v for k, v in extra_search_index.items()}
matches = real_search(user_input, lower_extra_search_index.keys(), cutoff, n)
cur_matched_collection += [lower_extra_search_index[match] for match in matches]
return cur_matched_collection[:n]
# 歌曲排序
def custom_sort_key(s):
# 使用正则表达式分别提取字符串的数字前缀和数字后缀
@ -596,3 +605,10 @@ def get_m4a_metadata(file_path):
metadata["picture"] = base64.b64encode(cover).decode("utf-8")
return metadata
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}"

View File

@ -12,6 +12,7 @@ import urllib.parse
from dataclasses import asdict
from logging.handlers import RotatingFileHandler
from pathlib import Path
from collections import OrderedDict
from aiohttp import ClientSession, ClientTimeout
from miservice import MiAccount, MiNAService
@ -49,16 +50,10 @@ from xiaomusic.utils import (
parse_str_to_dict,
remove_id3_tags,
traverse_music_directory,
list2str,
)
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
@ -77,6 +72,7 @@ class XiaoMusic:
self.devices = {} # key 为 did
self.running_task = []
self.all_music_tags = {} # 歌曲额外信息
self._extra_index_search = {}
# 初始化配置
self.init_config()
@ -474,23 +470,26 @@ class XiaoMusic:
# self.log.debug(self.all_music)
self.music_list = {}
for dir_name, musics in all_music_by_dir.items():
self.music_list[dir_name] = list(musics.keys())
# self.log.debug("dir_name:%s, list:%s", dir_name, self.music_list[dir_name])
try:
self._append_music_list()
except Exception as e:
self.log.exception(f"Execption {e}")
self.music_list = OrderedDict({"临时搜索列表": []})
# 全部,所有,自定义歌单(收藏)
self.music_list["全部"] = list(self.all_music.keys())
self.music_list["所有歌曲"] = [
name for name in self.all_music.keys() if name not in self._all_radio
]
self._append_custom_play_list()
# 网络歌单
try:
# NOTE: 函数内会更新 self.all_music, self._music_list重建 self._all_radio
self._append_music_list()
except Exception as e:
self.log.exception(f"Execption {e}")
# 文件夹歌单
for dir_name, musics in all_music_by_dir.items():
self.music_list[dir_name] = list(musics.keys())
# self.log.debug("dir_name:%s, list:%s", dir_name, self.music_list[dir_name])
# 歌单排序
for _, play_list in self.music_list.items():
play_list.sort(key=custom_sort_key)
@ -499,6 +498,13 @@ class XiaoMusic:
for device in self.devices.values():
device.update_playlist()
# 重建索引
self._extra_index_search = {}
for k, v in self.all_music.items():
# 如果不是 url则增加索引
if not (v.startswith("http") or v.startswith("https")):
self._extra_index_search[v] = k
def _append_custom_play_list(self):
if not self.config.custom_play_list_json:
return
@ -682,12 +688,14 @@ class XiaoMusic:
all_music_list = list(self.all_music.keys())
real_names = find_best_match(
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n,
extra_search_index=self._extra_index_search,
)
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,
extra_search_index=self._extra_index_search,
)
random.shuffle(real_names)
real_names = real_names[:n]
@ -739,9 +747,10 @@ class XiaoMusic:
self.log.debug("没开启模糊匹配")
return list_name
# 模糊搜一个播放列表
# 模糊搜一个播放列表(只需要一个,不需要 extra index
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,
n=1,
)[0]
if real_name:
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}")
@ -871,7 +880,7 @@ class XiaoMusic:
# 搜索音乐
def searchmusic(self, name):
all_music_list = list(self.all_music.keys())
search_list = fuzzyfinder(name, all_music_list)
search_list = fuzzyfinder(name, all_music_list, self._extra_index_search)
self.log.debug(f"searchmusic. name:{name} search_list:{search_list}")
return search_list
@ -1038,23 +1047,29 @@ class XiaoMusicDevice:
return offset, duration
# 初始化播放列表
def update_playlist(self):
def update_playlist(self, reorder=True):
# 没有重置 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])
if self.device.play_type == PLAY_TYPE_RND:
random.shuffle(self._play_list)
self.log.info(f"随机打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
if self.device.cur_playlist == "临时搜索列表" and len(self._play_list) > 0:
# 更新总播放列表为了UI显示
self.xiaomusic.music_list['临时搜索列表'] = copy.copy(self._play_list)
elif (self.device.cur_playlist == "临时搜索列表" and len(self._play_list) == 0
) or (self.device.cur_playlist not in self.xiaomusic.music_list):
self.device.cur_playlist = "全部"
else:
self._play_list = sorted(self._play_list)
self.log.info(f"没打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
pass # 指定了已知的播放列表名称
list_name = self.device.cur_playlist
self._play_list = copy.copy(self.xiaomusic.music_list[list_name])
if reorder:
if self.device.play_type == PLAY_TYPE_RND:
random.shuffle(self._play_list)
self.log.info(f"随机打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
else:
self._play_list = sorted(self._play_list)
self.log.info(f"没打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
else:
self.log.info(f"更新 {list_name} {list2str(self._play_list, self.config.verbose)}")
# 播放歌曲
async def play(self, name="", search_key=""):
@ -1078,10 +1093,12 @@ class XiaoMusicDevice:
if len(names) > 0:
if update_cur and len(names) > 1: # 大于一首歌才更新
self._play_list = names
self.device.cur_playlist = "当前"
self.device.cur_playlist = "临时搜索列表"
self.update_playlist()
elif update_cur: # 只有一首歌append
self._play_list = self._play_list + names
self.device.cur_playlist = "临时搜索列表"
self.update_playlist(reorder=False)
name = names[0]
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
elif not self.xiaomusic.is_music_exist(name):
@ -1154,10 +1171,12 @@ class XiaoMusicDevice:
if len(names) > 0:
if len(names) > 1: # 大于一首歌才更新
self._play_list = names
self.device.cur_playlist = "当前"
self.device.cur_playlist = "临时搜索列表"
self.update_playlist()
else: # 只有一首歌append
self._play_list = self._play_list + names
self.device.cur_playlist = "临时搜索列表"
self.update_playlist(reorder=False)
name = names[0]
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
elif not self.xiaomusic.is_music_exist(name):