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):
|
def fuzzyfinder(user_input, collection, extra_search_index=None):
|
||||||
lower_collection = {item.lower(): item for item in collection}
|
return find_best_match(user_input, collection, cutoff=0.1, n=10,
|
||||||
user_input = user_input.lower()
|
extra_search_index=extra_search_index)
|
||||||
matches = difflib.get_close_matches(
|
|
||||||
user_input, lower_collection.keys(), n=10, cutoff=0.1
|
|
||||||
)
|
|
||||||
return [lower_collection[match] for match in matches]
|
|
||||||
|
|
||||||
|
|
||||||
# 关键词检测
|
# 关键词检测
|
||||||
@ -112,16 +108,29 @@ def keyword_detection(user_input, str_list, n):
|
|||||||
return random.sample(matched, n), remains
|
return random.sample(matched, n), remains
|
||||||
|
|
||||||
|
|
||||||
def find_best_match(user_input, collection, cutoff=0.6, n=1):
|
def real_search(prompt, candidates, cutoff, n):
|
||||||
lower_collection = {item.lower(): item for item in collection}
|
matches, remains = keyword_detection(prompt, candidates, n=n)
|
||||||
user_input = user_input.lower()
|
|
||||||
matches, remains = keyword_detection(user_input, lower_collection.keys(), n=n)
|
|
||||||
if len(matches) < n:
|
if len(matches) < n:
|
||||||
# 如果没有准确关键词匹配,开始模糊匹配
|
# 如果没有准确关键词匹配,开始模糊匹配
|
||||||
matches += difflib.get_close_matches(
|
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]
|
||||||
|
|
||||||
|
|
||||||
# 歌曲排序
|
# 歌曲排序
|
||||||
@ -596,3 +605,10 @@ def get_m4a_metadata(file_path):
|
|||||||
metadata["picture"] = base64.b64encode(cover).decode("utf-8")
|
metadata["picture"] = base64.b64encode(cover).decode("utf-8")
|
||||||
|
|
||||||
return metadata
|
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 dataclasses import asdict
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from aiohttp import ClientSession, ClientTimeout
|
from aiohttp import ClientSession, ClientTimeout
|
||||||
from miservice import MiAccount, MiNAService
|
from miservice import MiAccount, MiNAService
|
||||||
@ -49,16 +50,10 @@ from xiaomusic.utils import (
|
|||||||
parse_str_to_dict,
|
parse_str_to_dict,
|
||||||
remove_id3_tags,
|
remove_id3_tags,
|
||||||
traverse_music_directory,
|
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:
|
class XiaoMusic:
|
||||||
def __init__(self, config: Config):
|
def __init__(self, config: Config):
|
||||||
self.config = config
|
self.config = config
|
||||||
@ -77,6 +72,7 @@ class XiaoMusic:
|
|||||||
self.devices = {} # key 为 did
|
self.devices = {} # key 为 did
|
||||||
self.running_task = []
|
self.running_task = []
|
||||||
self.all_music_tags = {} # 歌曲额外信息
|
self.all_music_tags = {} # 歌曲额外信息
|
||||||
|
self._extra_index_search = {}
|
||||||
|
|
||||||
# 初始化配置
|
# 初始化配置
|
||||||
self.init_config()
|
self.init_config()
|
||||||
@ -474,23 +470,26 @@ class XiaoMusic:
|
|||||||
|
|
||||||
# self.log.debug(self.all_music)
|
# self.log.debug(self.all_music)
|
||||||
|
|
||||||
self.music_list = {}
|
self.music_list = OrderedDict({"临时搜索列表": []})
|
||||||
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["全部"] = list(self.all_music.keys())
|
self.music_list["全部"] = list(self.all_music.keys())
|
||||||
self.music_list["所有歌曲"] = [
|
self.music_list["所有歌曲"] = [
|
||||||
name for name in self.all_music.keys() if name not in self._all_radio
|
name for name in self.all_music.keys() if name not in self._all_radio
|
||||||
]
|
]
|
||||||
|
|
||||||
self._append_custom_play_list()
|
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():
|
for _, play_list in self.music_list.items():
|
||||||
play_list.sort(key=custom_sort_key)
|
play_list.sort(key=custom_sort_key)
|
||||||
@ -499,6 +498,13 @@ class XiaoMusic:
|
|||||||
for device in self.devices.values():
|
for device in self.devices.values():
|
||||||
device.update_playlist()
|
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):
|
def _append_custom_play_list(self):
|
||||||
if not self.config.custom_play_list_json:
|
if not self.config.custom_play_list_json:
|
||||||
return
|
return
|
||||||
@ -682,12 +688,14 @@ class XiaoMusic:
|
|||||||
all_music_list = list(self.all_music.keys())
|
all_music_list = list(self.all_music.keys())
|
||||||
real_names = find_best_match(
|
real_names = find_best_match(
|
||||||
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n,
|
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n,
|
||||||
|
extra_search_index=self._extra_index_search,
|
||||||
)
|
)
|
||||||
if real_names:
|
if real_names:
|
||||||
if n > 1:
|
if n > 1:
|
||||||
# 扩大范围再找,最后保留随机 n 个
|
# 扩大范围再找,最后保留随机 n 个
|
||||||
real_names = find_best_match(
|
real_names = find_best_match(
|
||||||
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n * 2,
|
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff, n=n * 2,
|
||||||
|
extra_search_index=self._extra_index_search,
|
||||||
)
|
)
|
||||||
random.shuffle(real_names)
|
random.shuffle(real_names)
|
||||||
real_names = real_names[:n]
|
real_names = real_names[:n]
|
||||||
@ -739,9 +747,10 @@ class XiaoMusic:
|
|||||||
self.log.debug("没开启模糊匹配")
|
self.log.debug("没开启模糊匹配")
|
||||||
return list_name
|
return list_name
|
||||||
|
|
||||||
# 模糊搜一个播放列表
|
# 模糊搜一个播放列表(只需要一个,不需要 extra index)
|
||||||
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,
|
||||||
|
n=1,
|
||||||
)[0]
|
)[0]
|
||||||
if real_name:
|
if real_name:
|
||||||
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}】")
|
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}】")
|
||||||
@ -871,7 +880,7 @@ class XiaoMusic:
|
|||||||
# 搜索音乐
|
# 搜索音乐
|
||||||
def searchmusic(self, name):
|
def searchmusic(self, name):
|
||||||
all_music_list = list(self.all_music.keys())
|
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}")
|
self.log.debug(f"searchmusic. name:{name} search_list:{search_list}")
|
||||||
return search_list
|
return search_list
|
||||||
|
|
||||||
@ -1038,23 +1047,29 @@ class XiaoMusicDevice:
|
|||||||
return offset, duration
|
return offset, duration
|
||||||
|
|
||||||
# 初始化播放列表
|
# 初始化播放列表
|
||||||
def update_playlist(self):
|
def update_playlist(self, reorder=True):
|
||||||
# 没有重置 list 且非初始化
|
# 没有重置 list 且非初始化
|
||||||
if self.device.cur_playlist == "当前" and len(self._play_list) > 0:
|
if self.device.cur_playlist == "临时搜索列表" and len(self._play_list) > 0:
|
||||||
list_name = "当前"
|
# 更新总播放列表,为了UI显示
|
||||||
else: # 调用了播放列表功能,cur_playlist 为新列表名称
|
self.xiaomusic.music_list['临时搜索列表'] = copy.copy(self._play_list)
|
||||||
if self.device.cur_playlist not in self.xiaomusic.music_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 = "全部"
|
self.device.cur_playlist = "全部"
|
||||||
|
else:
|
||||||
|
pass # 指定了已知的播放列表名称
|
||||||
|
|
||||||
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 reorder:
|
||||||
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} {list2str(self._play_list, self.config.verbose)}")
|
self.log.info(f"随机打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
|
||||||
else:
|
else:
|
||||||
self._play_list = sorted(self._play_list)
|
self._play_list = sorted(self._play_list)
|
||||||
self.log.info(f"没打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
|
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=""):
|
async def play(self, name="", search_key=""):
|
||||||
@ -1078,10 +1093,12 @@ class XiaoMusicDevice:
|
|||||||
if len(names) > 0:
|
if len(names) > 0:
|
||||||
if update_cur and len(names) > 1: # 大于一首歌才更新
|
if update_cur and len(names) > 1: # 大于一首歌才更新
|
||||||
self._play_list = names
|
self._play_list = names
|
||||||
self.device.cur_playlist = "当前"
|
self.device.cur_playlist = "临时搜索列表"
|
||||||
self.update_playlist()
|
self.update_playlist()
|
||||||
elif update_cur: # 只有一首歌,append
|
elif update_cur: # 只有一首歌,append
|
||||||
self._play_list = self._play_list + names
|
self._play_list = self._play_list + names
|
||||||
|
self.device.cur_playlist = "临时搜索列表"
|
||||||
|
self.update_playlist(reorder=False)
|
||||||
name = names[0]
|
name = names[0]
|
||||||
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
|
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
|
||||||
elif not self.xiaomusic.is_music_exist(name):
|
elif not self.xiaomusic.is_music_exist(name):
|
||||||
@ -1154,10 +1171,12 @@ class XiaoMusicDevice:
|
|||||||
if len(names) > 0:
|
if len(names) > 0:
|
||||||
if len(names) > 1: # 大于一首歌才更新
|
if len(names) > 1: # 大于一首歌才更新
|
||||||
self._play_list = names
|
self._play_list = names
|
||||||
self.device.cur_playlist = "当前"
|
self.device.cur_playlist = "临时搜索列表"
|
||||||
self.update_playlist()
|
self.update_playlist()
|
||||||
else: # 只有一首歌,append
|
else: # 只有一首歌,append
|
||||||
self._play_list = self._play_list + names
|
self._play_list = self._play_list + names
|
||||||
|
self.device.cur_playlist = "临时搜索列表"
|
||||||
|
self.update_playlist(reorder=False)
|
||||||
name = names[0]
|
name = names[0]
|
||||||
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
|
self.log.debug(f"当前播放列表为:{list2str(self._play_list, self.config.verbose)}")
|
||||||
elif not self.xiaomusic.is_music_exist(name):
|
elif not self.xiaomusic.is_music_exist(name):
|
||||||
|
Loading…
Reference in New Issue
Block a user