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:
parent
c1915fb6b1
commit
a428f377d4
@ -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}"
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user