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): 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,18 +108,31 @@ 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]
# 歌曲排序 # 歌曲排序
def custom_sort_key(s): def custom_sort_key(s):
# 使用正则表达式分别提取字符串的数字前缀和数字后缀 # 使用正则表达式分别提取字符串的数字前缀和数字后缀
@ -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}"

View File

@ -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
self.device.cur_playlist = "全部" ) or (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)}")
else: else:
self._play_list = sorted(self._play_list) pass # 指定了已知的播放列表名称
self.log.info(f"没打乱 {list_name} {list2str(self._play_list, self.config.verbose)}")
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=""): 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):