mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-06 14:52:50 +08:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82aa453e50 | ||
|
|
1718211619 | ||
|
|
09310675fc | ||
|
|
ca711bbdb8 | ||
|
|
fb44f88df2 | ||
|
|
e5b32b2831 | ||
|
|
19ddbb7ca9 | ||
|
|
3e82d7acdc | ||
|
|
3921c70c86 | ||
|
|
aea9333e57 | ||
|
|
7043ca31cf | ||
|
|
963d86de7c | ||
|
|
d6b0e974b7 | ||
|
|
8a8340a159 | ||
|
|
20d3c9fce9 | ||
|
|
729549a7a9 | ||
|
|
d28614177c | ||
|
|
db53517784 | ||
|
|
186e9c1417 | ||
|
|
7498016d61 | ||
|
|
7114ea2e6e | ||
|
|
e8ca1f8678 | ||
|
|
2275bc2600 | ||
|
|
45a94f4bfe | ||
|
|
5cb2c84715 | ||
|
|
a3bf8d8aaa | ||
|
|
0a88b7be26 | ||
|
|
05da3e8fc4 | ||
|
|
560ea1aeca | ||
|
|
5ef1f2d940 | ||
|
|
e01fcdcecd | ||
|
|
8ff04dd0f6 | ||
|
|
4a7b5ac2b0 | ||
|
|
ac74ef3c15 | ||
|
|
8913057c27 | ||
|
|
a75030a73c | ||
|
|
e2e74f9459 | ||
|
|
09febb66dc | ||
|
|
ad0db8c9a9 |
84
CHANGELOG.md
84
CHANGELOG.md
@@ -1,3 +1,87 @@
|
||||
## v0.3.9 (2024-07-17)
|
||||
|
||||
### Feat
|
||||
|
||||
- #119 音乐目录支持软连接
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复日志下载报错问题
|
||||
- 兼容旧的setting.json文件中conf_path为空的情况
|
||||
- 修复设置页面可能打不开的问题
|
||||
|
||||
## v0.3.8 (2024-07-16)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复播放url接口问题
|
||||
|
||||
## v0.3.7 (2024-07-16)
|
||||
|
||||
### Feat
|
||||
|
||||
- 播放链接按钮对应给个默认的链接用于测试
|
||||
- Uvicorn 的日志信息合并到 xiaomusic 日志里显示
|
||||
|
||||
## v0.3.6 (2024-07-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- #126 修复pip安装时主页打不开的问题
|
||||
|
||||
## v0.3.5 (2024-07-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- #116 播放失败自动切下首歌
|
||||
|
||||
## v0.3.4 (2024-07-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- #125 修复本地英文歌曲匹大小写字母配不到的问题
|
||||
|
||||
## v0.3.3 (2024-07-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- 尝试修复播放卡顿问题 see #124
|
||||
|
||||
## v0.3.2 (2024-07-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- #122 pip安装方式下,static目录找不到报错
|
||||
- 版本更新时更新页面缓存
|
||||
|
||||
## v0.3.1 (2024-07-15)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复主页选择设备不生效的问题 see #120
|
||||
|
||||
## v0.3.0 (2024-07-14)
|
||||
|
||||
### Feat
|
||||
|
||||
- 建议音乐目录和配置目录分开不同目录
|
||||
- 优化后台网络设置,同时支持ipv4和ipv6
|
||||
- 使用fastapi替换flask,解决多线程问题
|
||||
- #106 网页上显示音箱当前状态(播放中or空闲中)以及当前的播放模式
|
||||
- 优化首页加载慢的问题
|
||||
- 优化设置页面布局,方便配置必须项
|
||||
- 优化配置界面,支持配置分组
|
||||
- 支持多设备分开播放 see #65
|
||||
|
||||
### Fix
|
||||
|
||||
- #114 修复部分 mp3 文件长度识别错误
|
||||
- 删除 armv6 的支持
|
||||
- 修复编译问题
|
||||
- 修复音乐路径设置后找不到音乐的问题
|
||||
- 修复启动报错的问题
|
||||
- 修复CI警告问题
|
||||
|
||||
## v0.2.0 (2024-07-09)
|
||||
|
||||
### Feat
|
||||
|
||||
@@ -16,7 +16,8 @@ COPY xiaomusic.py .
|
||||
ENV XDG_CONFIG_HOME=/config
|
||||
ENV XIAOMUSIC_HOSTNAME=192.168.2.5
|
||||
ENV XIAOMUSIC_PORT=8090
|
||||
VOLUME /config
|
||||
VOLUME /app/conf
|
||||
VOLUME /app/music
|
||||
EXPOSE 8090
|
||||
ENV PATH=/app/.venv/bin:$PATH
|
||||
ENTRYPOINT [".venv/bin/python3","xiaomusic.py"]
|
||||
|
||||
12
README.md
12
README.md
@@ -29,6 +29,7 @@ services:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
- ./conf:/app/conf
|
||||
```
|
||||
|
||||
对应的 docker 启动命令如下:
|
||||
@@ -36,9 +37,12 @@ services:
|
||||
```yaml
|
||||
docker run -p 8090:8090 \
|
||||
-v ./music:/app/music \
|
||||
-v ./conf:/app/conf
|
||||
hanxi/xiaomusic
|
||||
```
|
||||
|
||||
其中 conf 目录为配置文件存放目录,music 目录为音乐存放目录,建议分开配置为不同的目录。
|
||||
|
||||
启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
|
||||
|
||||
### ✨✨✨ 修改默认8090端口映射 ✨✨✨
|
||||
@@ -116,6 +120,8 @@ export XIAOMUSIC_SEARCH='bilisearch:'
|
||||
pdm run xiaomusic.py
|
||||
````
|
||||
|
||||
如果是开发前端界面,可以通过 <http://localhost:8090/docs> 查看有什么接口。
|
||||
|
||||
### 支持口令
|
||||
|
||||
- **播放歌曲**
|
||||
@@ -146,11 +152,13 @@ pdm run xiaomusic.py
|
||||
| LX01 | [小爱音箱mini](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx01) |
|
||||
| L05B | [小爱音箱Play](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05b) |
|
||||
| L05C | [小米小爱音箱Play 增强版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05c) |
|
||||
| LX04 X10A X08A | 已经支持的触屏版 |
|
||||
|
||||
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>
|
||||
|
||||
> 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。
|
||||
> 目前应该所有设备类型都已经支持播放,有问题随时反馈。
|
||||
> 其他触屏版不能播放可以设置 XIAOMUSIC_USE_MUSIC_API 为 true 试试。
|
||||
|
||||
## 支持音乐格式
|
||||
|
||||
@@ -314,9 +322,9 @@ services:
|
||||
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,具体见 <https://github.com/hanxi/xiaomusic/issues/47>
|
||||
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户
|
||||
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
|
||||
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认情况会把配置存放在music目录,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
|
||||
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认为 `/app/config` ,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
|
||||
- XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,见 <https://github.com/hanxi/xiaomusic/issues/82>
|
||||
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,如果发现需要设置设置选项的时候请告知我加一下设备型号,方便以后不用设置。
|
||||
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 <https://github.com/hanxi/xiaomusic/issues/30>
|
||||
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
||||
|
||||
9
newpatch.sh
Executable file
9
newpatch.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
./update-static-version.py
|
||||
git add xiaomusic/static
|
||||
git commit -m 'build: update static version'
|
||||
|
||||
cz bump --check-consistency --increment patch
|
||||
|
||||
git push -u origin main --tags
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
cz bump --check-consistency --increment patch
|
||||
./update-static-version.py
|
||||
git add xiaomusic/static
|
||||
git commit -m 'build: update static version'
|
||||
|
||||
cz bump --check-consistency
|
||||
|
||||
git push -u origin main --tags
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "xiaomusic"
|
||||
version = "0.2.0"
|
||||
version = "0.3.9"
|
||||
description = "Play Music with xiaomi AI speaker"
|
||||
authors = [
|
||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||
|
||||
57
update-static-version.py
Executable file
57
update-static-version.py
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_html_files(directory):
|
||||
"""
|
||||
获取指定目录下所有HTML文件的列表。
|
||||
|
||||
:param directory: 搜索HTML文件的目录。
|
||||
:return: 搜索到的HTML文件的路径列表。
|
||||
"""
|
||||
return list(Path(directory).rglob("*.html"))
|
||||
|
||||
|
||||
def update_html_version(html_files, version):
|
||||
"""
|
||||
更新HTML文件中所有以 /static/ 开头的CSS和JS文件引用的版本号。
|
||||
|
||||
:param html_files: 需要更新的HTML文件路径的列表。
|
||||
:param version: 新的版本号字符串。
|
||||
"""
|
||||
pattern = re.compile(r'(/static/.*(css|js))\?version=[^"]*"')
|
||||
# pattern = re.compile(r'(/static/.*html)\?version=[^"]*"')
|
||||
|
||||
for html_file in html_files:
|
||||
if not html_file.exists():
|
||||
print(f"文件 {html_file} 不存在。")
|
||||
continue
|
||||
|
||||
html_content = html_file.read_text()
|
||||
|
||||
# 更新CSS和JS版本号
|
||||
html_content = pattern.sub(rf'\g<1>?version={version}"', html_content)
|
||||
# html_content = pattern.sub(fr'\g<1>"', html_content)
|
||||
|
||||
# 保存更改到HTML文件
|
||||
html_file.write_text(html_content)
|
||||
|
||||
print(f"文件 {html_file} 已更新为使用新的版本号: {version}")
|
||||
|
||||
|
||||
# 使用案例
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
t = str(int(time.time()))
|
||||
|
||||
# 指定目录
|
||||
html_directory = "xiaomusic/static" # 修改为实际的HTML文件目录路径
|
||||
|
||||
# 获取HTML文件列表
|
||||
html_files_to_update = get_html_files(html_directory)
|
||||
|
||||
# 执行更新
|
||||
update_html_version(html_files_to_update, t)
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.2.0"
|
||||
__version__ = "0.3.9"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
|
||||
import uvicorn
|
||||
@@ -77,7 +78,26 @@ def main():
|
||||
xiaomusic = XiaoMusic(config)
|
||||
HttpInit(xiaomusic)
|
||||
|
||||
uvicorn.run(HttpApp, host=["::", "0.0.0.0"], port=config.port)
|
||||
from uvicorn.config import LOGGING_CONFIG
|
||||
|
||||
LOGGING_CONFIG["formatters"]["access"] = {
|
||||
"format": f"%(asctime)s [{__version__}] [%(levelname)s] %(filename)s:%(lineno)d: %(message)s",
|
||||
"datefmt": "[%X]",
|
||||
}
|
||||
LOGGING_CONFIG["handlers"]["access"] = {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "access",
|
||||
"filename": config.log_file,
|
||||
"maxBytes": 10 * 1024 * 1024,
|
||||
"backupCount": 1,
|
||||
}
|
||||
uvicorn.run(
|
||||
HttpApp,
|
||||
host=["::", "0.0.0.0"],
|
||||
port=config.port,
|
||||
log_config=LOGGING_CONFIG,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,15 +2,17 @@ import asyncio
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import shutil
|
||||
import tempfile
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
from starlette.background import BackgroundTask
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
from xiaomusic import __version__
|
||||
@@ -75,6 +77,10 @@ def reset_http_server():
|
||||
else:
|
||||
app.dependency_overrides = {}
|
||||
|
||||
# 更新 music 链接
|
||||
app.router.routes = [route for route in app.router.routes if route.path != "/music"]
|
||||
app.mount("/music", StaticFiles(directory=config.music_path), name="music")
|
||||
|
||||
|
||||
def HttpInit(_xiaomusic):
|
||||
global xiaomusic, config, log
|
||||
@@ -82,30 +88,15 @@ def HttpInit(_xiaomusic):
|
||||
config = xiaomusic.config
|
||||
log = xiaomusic.log
|
||||
|
||||
app.mount("/static", StaticFiles(directory="xiaomusic/static"), name="static")
|
||||
folder = os.path.dirname(__file__)
|
||||
app.mount("/static", StaticFiles(directory=f"{folder}/static"), name="static")
|
||||
reset_http_server()
|
||||
|
||||
|
||||
@app.get("/music/{file_path:path}")
|
||||
async def read_music_file(file_path: str):
|
||||
base_dir = os.path.abspath(config.music_path)
|
||||
real_path = os.path.normpath(os.path.join(base_dir, file_path))
|
||||
log.info(f"read_music_file. file_path:{file_path} real_path:{real_path}")
|
||||
if not real_path.startswith(base_dir):
|
||||
raise HTTPException(
|
||||
status_code=403, detail="Access to this file is not permitted"
|
||||
)
|
||||
|
||||
file_location = Path(real_path).resolve()
|
||||
if not file_location.exists() or not file_location.is_file():
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
return FileResponse(file_location)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_index():
|
||||
return FileResponse("xiaomusic/static/index.html")
|
||||
folder = os.path.dirname(__file__)
|
||||
return FileResponse(f"{folder}/static/index.html")
|
||||
|
||||
|
||||
@app.get("/getversion")
|
||||
@@ -251,7 +242,29 @@ async def downloadjson(data: UrlInfo):
|
||||
def downloadlog(Verifcation=Depends(verification)):
|
||||
file_path = xiaomusic.config.log_file
|
||||
if os.path.exists(file_path):
|
||||
return FileResponse(path=file_path, media_type="text/plain")
|
||||
# 创建一个临时文件来保存日志的快照
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
try:
|
||||
with open(file_path, "rb") as f:
|
||||
shutil.copyfileobj(f, temp_file)
|
||||
temp_file.close()
|
||||
|
||||
# 使用BackgroundTask在响应发送完毕后删除临时文件
|
||||
def cleanup_temp_file(tmp_file_path):
|
||||
os.remove(tmp_file_path)
|
||||
|
||||
background_task = BackgroundTask(cleanup_temp_file, temp_file.name)
|
||||
return FileResponse(
|
||||
temp_file.name,
|
||||
media_type="text/plain",
|
||||
filename="xiaomusic.txt",
|
||||
background=background_task,
|
||||
)
|
||||
except Exception as e:
|
||||
os.remove(temp_file.name)
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Error capturing log file"
|
||||
) from e
|
||||
else:
|
||||
return {"message": "File not found."}
|
||||
|
||||
@@ -262,9 +275,7 @@ async def playurl(did: str, url: str):
|
||||
return {"ret": "Did not exist"}
|
||||
|
||||
log.info(f"playurl did: {did} url: {url}")
|
||||
return await xiaomusic.call_main_thread_function(
|
||||
xiaomusic.play_url, did=did, arg1=url
|
||||
)
|
||||
return await xiaomusic.play_url(did=did, arg1=url)
|
||||
|
||||
|
||||
@app.post("/debug_play_by_music_url")
|
||||
|
||||
@@ -24,8 +24,13 @@ $(function(){
|
||||
localStorage.setItem('mi_did', data.mi_did);
|
||||
|
||||
var did = localStorage.getItem('cur_did');
|
||||
if ((did == null || did == "") && data.mi_did != null) {
|
||||
var dids = data.mi_did.split(',');
|
||||
var dids = [];
|
||||
if (data.mi_did != null) {
|
||||
dids = data.mi_did.split(',');
|
||||
}
|
||||
console.log('cur_did', did);
|
||||
console.log('dids', dids);
|
||||
if ((dids.length > 0) && (did == null || did == "" || !dids.includes(did))) {
|
||||
did = dids[0];
|
||||
localStorage.setItem('cur_did', did);
|
||||
}
|
||||
@@ -60,6 +65,14 @@ $(function(){
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('cur_did', did);
|
||||
$('#did').change(function() {
|
||||
did = $(this).val();
|
||||
localStorage.setItem('cur_did', did);
|
||||
window.did = did;
|
||||
console.log('cur_did', did);
|
||||
})
|
||||
});
|
||||
|
||||
// 拉取版本
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Debug For XiaoMusic</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script src="/static/jquery-3.7.1.min.js"></script>
|
||||
<script src="/static/jquery-3.7.1.min.js?version=1721213953"></script>
|
||||
|
||||
<script>
|
||||
var vConsole = new window.VConsole();
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="/static/jquery-3.7.1.min.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<script src="/static/jquery-3.7.1.min.js?version=1721213953"></script>
|
||||
<script src="/static/app.js?version=1721213953"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
|
||||
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
@@ -65,7 +65,7 @@ var vConsole = new window.VConsole();
|
||||
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<input id="music-url" type="text" placeholder="链接(http://ngcdn001.cnr.cn/live/zgzs/index.m3u8)"></input>
|
||||
<input id="music-url" type="text" value="https://lhttp.qtfm.cn/live/4915/64k.mp3"></input>
|
||||
<button id="playurl">播放链接</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>M3U to JSON Converter</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="/static/jquery-3.7.1.min.js"></script>
|
||||
<script src="/static/setting.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<script src="/static/jquery-3.7.1.min.js?version=1721213953"></script>
|
||||
<script src="/static/setting.js?version=1721213953"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
|
||||
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
|
||||
@@ -73,12 +73,21 @@ def validate_proxy(proxy_str: str) -> bool:
|
||||
|
||||
# 模糊搜索
|
||||
def fuzzyfinder(user_input, collection):
|
||||
return difflib.get_close_matches(user_input, collection, n=10, cutoff=0.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=10, cutoff=0.1
|
||||
)
|
||||
return [lower_collection[match] for match in matches]
|
||||
|
||||
|
||||
def find_best_match(user_input, collection, cutoff=0.6):
|
||||
matches = difflib.get_close_matches(user_input, collection, n=1, cutoff=cutoff)
|
||||
return matches[0] if matches else None
|
||||
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
|
||||
|
||||
|
||||
# 歌曲排序
|
||||
@@ -127,11 +136,23 @@ def _append_files_result(result, root, joinpath, files, support_extension):
|
||||
result[dir_name].append(os.path.join(joinpath, file))
|
||||
|
||||
|
||||
def custom_walk(top, followlinks=True):
|
||||
for root, dirs, files in os.walk(top, followlinks=False):
|
||||
yield root, dirs, files
|
||||
|
||||
for dir in dirs:
|
||||
path = os.path.join(root, dir)
|
||||
if followlinks and os.path.islink(path):
|
||||
# 如果启用 followlinks 并且是符号链接,获取其实际路径
|
||||
real_path = os.readlink(path)
|
||||
yield from custom_walk(real_path, followlinks=followlinks)
|
||||
|
||||
|
||||
def traverse_music_directory(
|
||||
directory, depth=10, exclude_dirs=None, support_extension=None
|
||||
):
|
||||
result = {}
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for root, dirs, files in custom_walk(directory):
|
||||
# 忽略排除的目录
|
||||
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
||||
|
||||
|
||||
@@ -87,6 +87,9 @@ class XiaoMusic:
|
||||
def init_config(self):
|
||||
self.music_path = self.config.music_path
|
||||
self.conf_path = self.config.conf_path
|
||||
# 兼容旧配置空的情况
|
||||
if not self.conf_path:
|
||||
self.conf_path = "conf"
|
||||
self.download_path = self.config.download_path
|
||||
if not self.download_path:
|
||||
self.download_path = self.music_path
|
||||
@@ -821,6 +824,7 @@ class XiaoMusicDevice:
|
||||
self._playing = False
|
||||
# 关机定时器
|
||||
self._stop_timer = None
|
||||
self._last_cmd = None
|
||||
self.update_playlist()
|
||||
|
||||
# 初始化播放列表
|
||||
@@ -832,6 +836,10 @@ class XiaoMusicDevice:
|
||||
|
||||
# 播放歌曲
|
||||
async def play(self, name="", search_key=""):
|
||||
self._last_cmd = "play"
|
||||
return await self._play(name=name, search_key=search_key)
|
||||
|
||||
async def _play(self, name="", search_key=""):
|
||||
if search_key == "" and name == "":
|
||||
if self.check_play_next():
|
||||
await self.play_next()
|
||||
@@ -855,6 +863,9 @@ class XiaoMusicDevice:
|
||||
|
||||
# 下一首
|
||||
async def play_next(self):
|
||||
return await self._play_next()
|
||||
|
||||
async def _play_next(self):
|
||||
self.log.info("开始播放下一首")
|
||||
name = self.cur_music
|
||||
if (
|
||||
@@ -867,10 +878,11 @@ class XiaoMusicDevice:
|
||||
if name == "":
|
||||
await self.do_tts("本地没有歌曲")
|
||||
return
|
||||
await self.play(name)
|
||||
await self._play(name)
|
||||
|
||||
# 播放本地歌曲
|
||||
async def playlocal(self, name):
|
||||
self._last_cmd = "playlocal"
|
||||
if name == "":
|
||||
if self.check_play_next():
|
||||
await self.play_next()
|
||||
@@ -894,7 +906,14 @@ class XiaoMusicDevice:
|
||||
sec, url = await self.xiaomusic.get_music_sec_url(name)
|
||||
await self.group_force_stop_xiaoai()
|
||||
self.log.info(f"播放 {url}")
|
||||
await self.group_player_play(url)
|
||||
results = await self.group_player_play(url)
|
||||
if all(ele is None for ele in results):
|
||||
self.log.info(f"播放 {name} 失败")
|
||||
await asyncio.sleep(1)
|
||||
if self.isplaying() and self._last_cmd != "stop":
|
||||
await self._play_next()
|
||||
return
|
||||
|
||||
self.log.info("已经开始播放了")
|
||||
|
||||
# 取消组内所有的下一首歌曲的定时器
|
||||
@@ -998,7 +1017,7 @@ class XiaoMusicDevice:
|
||||
if self.isplaying() and not self.isdownloading():
|
||||
# 继续播放歌曲
|
||||
self.log.info("现在继续播放歌曲")
|
||||
await self.play()
|
||||
await self._play()
|
||||
else:
|
||||
self.log.info(
|
||||
f"不会继续播放歌曲. isplaying:{self.isplaying()} isdownloading:{self.isdownloading()}"
|
||||
@@ -1141,12 +1160,14 @@ class XiaoMusicDevice:
|
||||
await self.do_tts(tts)
|
||||
|
||||
async def play_music_list(self, list_name, music_name):
|
||||
self._last_cmd = "play_music_list"
|
||||
self._cur_play_list = list_name
|
||||
self._play_list = self.xiaomusic.music_list[list_name]
|
||||
self.log.info(f"开始播放列表{list_name}")
|
||||
await self.play(music_name)
|
||||
await self._play(music_name)
|
||||
|
||||
async def stop(self, arg1=""):
|
||||
self._last_cmd = "stop"
|
||||
self._playing = False
|
||||
if arg1 != "notts":
|
||||
await self.do_tts(self.config.stop_tts_msg)
|
||||
|
||||
Reference in New Issue
Block a user