1
0
mirror of https://github.com/hanxi/xiaomusic.git synced 2025-12-06 14:52:50 +08:00

Compare commits

...

36 Commits

Author SHA1 Message Date
涵曦
0db3e4cada bump: version 0.3.54 → 0.3.55 2024-12-04 10:22:27 +08:00
涵曦
fa21d02e1d build: update static version 2024-12-04 10:22:26 +08:00
涵曦
be48fe9f54 fix: 修复播放接口报错问题 2024-12-04 10:17:06 +08:00
徒言
042a91336e chore: 引导页新增小程序码 (#290) 2024-12-04 09:54:38 +08:00
涵曦
790a4cd808 bump: version 0.3.53 → 0.3.54 2024-12-04 01:41:29 +08:00
涵曦
70d029f4d1 build: update static version 2024-12-04 01:41:28 +08:00
52fisher
0472ad3188 fix: 安卓低版本webview对audio的src为空值的报错 (#289) 2024-12-04 01:30:07 +08:00
涵曦
733c44d12f feat: 新增最近新增歌单 close #273 2024-12-03 21:44:48 +08:00
涵曦
8c92afd09b fix: 修复M01语音播放问题,X08C X08E X8F 型号默认采用型号兼容模式 see #30 2024-12-03 21:25:51 +08:00
涵曦
742929b9d5 Update README.md 2024-12-03 21:18:26 +08:00
涵曦
4766e08ff4 Update README.md 2024-12-03 21:17:11 +08:00
涵曦
99c4296c7a bump: version 0.3.52 → 0.3.53 2024-12-03 12:15:09 +08:00
涵曦
0ae24f4810 build: update static version 2024-12-03 12:15:08 +08:00
涵曦
77cca0d18c fix: 解决播放接口修改后播放失败的问题 2024-12-03 12:14:57 +08:00
涵曦
ddc24595df bump: version 0.3.51 → 0.3.52 2024-12-03 12:02:39 +08:00
涵曦
ce1bfb164c build: update static version 2024-12-03 12:02:39 +08:00
涵曦
78dbba6c1f fix: 修复播放接口参数错误的问题 2024-12-03 12:02:14 +08:00
涵曦
7eb0ab1e46 bump: version 0.3.50 → 0.3.51 2024-12-03 09:06:51 +08:00
涵曦
0a2e3cc579 build: update static version 2024-12-03 09:06:50 +08:00
涵曦
75ec336285 fix: 修复空配置启动失败问题 close #284 2024-12-03 09:02:15 +08:00
涵曦
a19d53f000 bump: version 0.3.49 → 0.3.50 2024-12-03 07:11:40 +08:00
涵曦
cdb6190e7a build: update static version 2024-12-03 07:11:40 +08:00
涵曦
1aa9b58561 style: 更新 gitignore 2024-12-02 23:17:54 +08:00
涵曦
6a990f48d0 fix: 更新 yt-dlp ,解决 B 站下载问题 close #279 2024-12-02 23:17:12 +08:00
涵曦
82e92a0380 Update README.md 2024-12-02 21:43:56 +08:00
涵曦
ecce5c8848 feat: 修改日志文件的默认值 2024-12-02 21:38:31 +08:00
涵曦
87fb34e5c9 feat: 新增修改tag缓存信息的接口 close #266 2024-12-02 12:27:09 +08:00
涵曦
df3c4b7fa9 feat: 新增专用的播放歌曲和播放歌单接口,解决默认口令提示词被修改了导致后台失效的问题 2024-12-02 11:40:26 +08:00
涵曦
cb2af0ee9f feat: 统计设备型号 2024-12-02 11:08:19 +08:00
涵曦
8f9bba0ca3 refactor: 调整设置页面 2024-12-01 22:47:56 +08:00
涵曦
b86e8d3196 Update README.md 2024-12-01 22:41:47 +08:00
52fisher
65067346f3 feat: 页面与设置中的HOST不一致时弹窗提醒 (#281) 2024-12-01 22:23:50 +08:00
52fisher
865b412fb7 fix: 网页播放audio获取到错误url无法播放时提醒用户 (#280) 2024-12-01 12:33:05 +08:00
涵曦
441fffd59e Update README.md 2024-11-29 10:02:51 +08:00
52fisher
2ee7b956cf feat: 未发现小爱设备时给予提示 (#278)
fix: input标签自闭合
2024-11-29 07:23:57 +08:00
涵曦
126bfa43a2 feat: 优化设置页面提示 2024-11-28 23:29:06 +08:00
22 changed files with 596 additions and 101 deletions

1
.gitignore vendored
View File

@@ -169,3 +169,4 @@ setting.json
.DS_Store
cache
tmp/
xiaomusic.log.txt

View File

@@ -1,3 +1,60 @@
## v0.3.55 (2024-12-04)
### Fix
- 修复播放接口报错问题
## v0.3.54 (2024-12-04)
### Feat
- 新增最近新增歌单 close #273
### Fix
- 安卓低版本webview对audio的src为空值的报错 (#289)
- 修复M01语音播放问题X08C X08E X8F 型号默认采用型号兼容模式 see #30
## v0.3.53 (2024-12-03)
### Fix
- 解决播放接口修改后播放失败的问题
## v0.3.52 (2024-12-03)
### Fix
- 修复播放接口参数错误的问题
## v0.3.51 (2024-12-03)
### Fix
- 修复空配置启动失败问题 close #284
## v0.3.50 (2024-12-03)
### Feat
- 修改日志文件的默认值
- 新增修改tag缓存信息的接口 close #266
- 新增专用的播放歌曲和播放歌单接口,解决默认口令提示词被修改了导致后台失效的问题
- 统计设备型号
- 页面与设置中的HOST不一致时弹窗提醒 (#281)
- 未发现小爱设备时给予提示 (#278)
- 优化设置页面提示
### Fix
- 更新 yt-dlp ,解决 B 站下载问题 close #279
- 网页播放audio获取到错误url无法播放时提醒用户 (#280)
- input标签自闭合
### Refactor
- 调整设置页面
## v0.3.49 (2024-11-28)
### Feat

View File

@@ -6,7 +6,8 @@
[![PyPI - Downloads](https://img.shields.io/pypi/dm/xiaomusic)](https://pypi.org/project/xiaomusic/)
[![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fhanxi%2Fxiaomusic%2Fmain%2Fpyproject.toml)](https://pypi.org/project/xiaomusic/)
[![GitHub Release](https://img.shields.io/github/v/release/hanxi/xiaomusic)](https://github.com/hanxi/xiaomusic/releases)
[![Visitors](https://api.visitorbadge.io/api/daily?path=hanxi%2Fxiaomusic&label=daily%20visitor&countColor=%232ccce4&style=flat)](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
[![Visitors](https://api.visitorbadge.io/api/visitors?path=hanxi%2Fxiaomusic&label=total%20visitor&countColor=%232ccce4&style=flat)](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
@@ -72,6 +73,29 @@ docker 和 docker compose 二选一即可,启动成功后,在 web 页面可
### 🔥 修改默认8090端口映射
#### 方法1 不修改监听端口 8090
【监听端口】保持为默认的 8090 不变,把【外网访问端口】改为 5678 。
```yaml
services:
xiaomusic:
image: hanxi/xiaomusic
container_name: xiaomusic
restart: unless-stopped
ports:
- 5678:8090
volumes:
- /xiaomusic/music:/app/music
- /xiaomusic/conf:/app/conf
environment:
XIAOMUSIC_PUBLIC_PORT: 5678
```
XIAOMUSIC_PUBLIC_PORT 对应后台设置里的【外网访问端口】,修改后可以不用重启。
#### 方法2 修改监听端口 8090 为 5678
如果需要修改 8090 端口为其他端口,比如 5678需要这样配3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
```yaml
@@ -94,7 +118,7 @@ services:
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
> [!IMPORTANT]
> XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口。
> XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口,修改后记得重启
### 🤐 支持语音口令
@@ -201,7 +225,8 @@ docker build -t xiaomusic .
| L05C | [小米小爱音箱Play 增强版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05c) |
| L09A | [小米音箱Art](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l09a) |
| LX04 X10A X08A | 已经支持的触屏版 |
| M01/XMYX01JY | 小米小爱音箱HD (获取对话记录的接口比较特殊) |
| X08C X08E X8F | 需要设置【型号兼容模式】选项为 true |
| M01/XMYX01JY | 小米小爱音箱HD 需要设置【特殊型号获取对话记录】选项为 true 才能语音播放|
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>

86
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default", "dev", "lint"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:37be8846356b5717495cac7252d78b1554985d607e286cf38ab863e307e5e25e"
content_hash = "sha256:d22d4b03450cd369683d3ddf827359cd96541ee09e29bfa1fa666d72aa7d3ab0"
[[metadata.targets]]
requires_python = "==3.10.12"
@@ -39,7 +39,7 @@ files = [
[[package]]
name = "aiohttp"
version = "3.11.8"
version = "3.11.9"
requires_python = ">=3.9"
summary = "Async http client/server framework (asyncio)"
groups = ["default"]
@@ -55,8 +55,8 @@ dependencies = [
"yarl<2.0,>=1.17.0",
]
files = [
{file = "aiohttp-3.11.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df9bf08eb93611b1d4d6245b6fecf88728e90eece00e00d554e1b0c445557d83"},
{file = "aiohttp-3.11.8.tar.gz", hash = "sha256:7bc9d64a2350cbb29a9732334e1a0743cbb6844de1731cbdf5949b235653f3fd"},
{file = "aiohttp-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39625703540feb50b6b7f938b3856d1f4886d2e585d88274e62b1bd273fae09b"},
{file = "aiohttp-3.11.9.tar.gz", hash = "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c"},
]
[[package]]
@@ -165,6 +165,17 @@ files = [
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
]
[[package]]
name = "brotli"
version = "1.1.0"
summary = "Python bindings for the Brotli compression library"
groups = ["default"]
marker = "implementation_name == \"cpython\" and python_full_version == \"3.10.12\""
files = [
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
]
[[package]]
name = "certifi"
version = "2024.8.30"
@@ -658,6 +669,18 @@ files = [
{file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"},
]
[[package]]
name = "pycryptodomex"
version = "3.21.0"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
summary = "Cryptographic library for Python"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"},
{file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"},
]
[[package]]
name = "pydantic"
version = "2.8.2"
@@ -760,14 +783,14 @@ files = [
[[package]]
name = "python-multipart"
version = "0.0.17"
version = "0.0.19"
requires_python = ">=3.8"
summary = "A streaming multipart parser for Python"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d"},
{file = "python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538"},
{file = "python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d"},
{file = "python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc"},
]
[[package]]
@@ -856,14 +879,14 @@ files = [
[[package]]
name = "ruff"
version = "0.8.0"
version = "0.8.1"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["lint"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"},
{file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
]
[[package]]
@@ -989,6 +1012,19 @@ files = [
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
]
[[package]]
name = "websockets"
version = "14.1"
requires_python = ">=3.9"
summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"},
{file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"},
{file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"},
]
[[package]]
name = "yarl"
version = "1.17.1"
@@ -1009,12 +1045,36 @@ files = [
[[package]]
name = "yt-dlp"
version = "2024.11.18"
version = "2024.12.1.232904.dev0"
requires_python = ">=3.9"
summary = "A feature-rich command-line audio/video downloader"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "yt_dlp-2024.11.18-py3-none-any.whl", hash = "sha256:b9741695911dc566498b5f115cdd6b1abbc5be61cb01fd98abe649990a41656c"},
{file = "yt_dlp-2024.11.18.tar.gz", hash = "sha256:b8a4c23d3c9afd7e476bcdb87f38b6c0e8e12e3a239d7988f13acb434200f54d"},
{file = "yt_dlp-2024.12.1.232904.dev0-py3-none-any.whl", hash = "sha256:67c1fa0986662f2f26ba70789c79c6d04fda45eb530944872f4a47ef4f94047e"},
{file = "yt_dlp-2024.12.1.232904.dev0.tar.gz", hash = "sha256:4fcce6637e70bad7c63220e4dd69a3a6b8969cbc17c93bfe569940dd173c0548"},
]
[[package]]
name = "yt-dlp"
version = "2024.12.1.232904.dev0"
extras = ["default"]
requires_python = ">=3.9"
summary = "A feature-rich command-line audio/video downloader"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
dependencies = [
"brotli; implementation_name == \"cpython\"",
"brotlicffi; implementation_name != \"cpython\"",
"certifi",
"mutagen",
"pycryptodomex",
"requests<3,>=2.32.2",
"urllib3<3,>=1.26.17",
"websockets>=13.0",
"yt-dlp==2024.12.1.232904.dev0",
]
files = [
{file = "yt_dlp-2024.12.1.232904.dev0-py3-none-any.whl", hash = "sha256:67c1fa0986662f2f26ba70789c79c6d04fda45eb530944872f4a47ef4f94047e"},
{file = "yt_dlp-2024.12.1.232904.dev0.tar.gz", hash = "sha256:4fcce6637e70bad7c63220e4dd69a3a6b8969cbc17c93bfe569940dd173c0548"},
]

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.3.49"
version = "0.3.55"
description = "Play Music with xiaomi AI speaker"
authors = [
{name = "涵曦", email = "im.hanxi@gmail.com"},
@@ -9,7 +9,7 @@ dependencies = [
"aiohttp>=3.8.6",
"miservice-fork>=2.7.0",
"mutagen>=1.47.0",
"yt-dlp>=2024.11.18",
"yt-dlp[default]>=2024.12.1.232904.dev0",
"uvicorn>=0.30.1",
"fastapi>=0.115.4",
"starlette>=0.37.2",

View File

@@ -1 +1 @@
__version__ = "0.3.49"
__version__ = "0.3.55"

View File

@@ -30,6 +30,7 @@ class Analytics:
async def run_with_cancel(self, func, *args, **kwargs):
try:
asyncio.ensure_future(asyncio.to_thread(func, *args, **kwargs))
self.log.info("analytics run_with_cancel success")
except Exception as e:
self.log.warning(f"analytics run_with_cancel failed {e}")
return None
@@ -66,17 +67,18 @@ class Analytics:
self.gtag.send(events=event_list)
self.current_date = current_date
async def send_play_event(self, name, sec):
async def send_play_event(self, name, sec, hardware):
try:
await self.run_with_cancel(self._send_play_event, name, sec)
await self.run_with_cancel(self._send_play_event, name, sec, hardware)
except Exception as e:
self.log.warning(f"analytics send_play_event failed {e}")
self.init()
def _send_play_event(self, name, sec):
def _send_play_event(self, name, sec, hardware):
event = self.gtag.create_new_event(name="play")
event.set_event_param(name="version", value=__version__)
event.set_event_param(name="music", value=name)
event.set_event_param(name="sec", value=sec)
event.set_event_param(name="hardware", value=hardware)
event_list = [event]
self.gtag.send(events=event_list)

View File

@@ -127,7 +127,7 @@ class Config:
"XIAOMUSIC_USE_MUSIC_AUDIO_ID", "1582971365183456177"
)
use_music_id: str = os.getenv("XIAOMUSIC_USE_MUSIC_ID", "355454500")
log_file: str = os.getenv("XIAOMUSIC_LOG_FILE", "/tmp/xiaomusic.txt")
log_file: str = os.getenv("XIAOMUSIC_LOG_FILE", "xiaomusic.log.txt")
# 模糊搜索匹配的最低相似度阈值
fuzzy_match_cutoff: float = float(os.getenv("XIAOMUSIC_FUZZY_MATCH_CUTOFF", "0.6"))
# 开启模糊搜索
@@ -186,6 +186,9 @@ class Config:
play_type_seq_tts_msg: str = os.getenv(
"XIAOMUSIC_PLAY_TYPE_SEQ_TTS_MSG", "已经设置为顺序播放"
)
recently_added_playlist_len: int = int(
os.getenv("XIAOMUSIC_RECENTLY_ADDED_PLAYLIST_LEN", "50")
)
def append_keyword(self, keys, action):
for key in keys.split(","):

View File

@@ -20,3 +20,10 @@ PLAY_TYPE_SEQ = 4 # 顺序播放
GET_ASK_BY_MINA = {
"M01",
}
# 需要使用 play_musci 接口的设备型号
NEED_USE_PLAY_MUSIC_API = {
"X08C",
"X08E",
"X8F",
}

View File

@@ -303,6 +303,23 @@ async def musicinfos(
return ret
class MusicInfoObj(BaseModel):
musicname: str
title: str = ""
artist: str = ""
album: str = ""
year: str = ""
genre: str = ""
lyrics: str = ""
picture: str = "" # base64
@app.post("/setmusictag")
async def setmusictag(info: MusicInfoObj, Verifcation=Depends(verification)):
ret = xiaomusic.set_music_tag(info.musicname, info)
return {"ret": ret}
@app.get("/curplaylist")
async def curplaylist(did: str = "", Verifcation=Depends(verification)):
if not xiaomusic.did_exist(did):
@@ -325,6 +342,44 @@ class UrlInfo(BaseModel):
url: str
class DidPlayMusic(BaseModel):
did: str
musicname: str = ""
searchkey: str = ""
@app.post("/playmusic")
async def playmusic(data: DidPlayMusic, Verifcation=Depends(verification)):
did = data.did
musicname = data.musicname
searchkey = data.searchkey
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
log.info(f"playmusic {did} musicname:{musicname} searchkey:{searchkey}")
await xiaomusic.do_play(did, musicname, searchkey)
return {"ret": "OK"}
class DidPlayMusicList(BaseModel):
did: str
listname: str = ""
musicname: str = ""
@app.post("/playmusiclist")
async def playmusiclist(data: DidPlayMusicList, Verifcation=Depends(verification)):
did = data.did
listname = data.listname
musicname = data.musicname
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
log.info(f"playmusiclist {did} listname:{listname} musicname:{musicname}")
await xiaomusic.do_play_music_list(did, listname, musicname)
return {"ret": "OK"}
@app.post("/downloadjson")
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
log.info(data)

View File

@@ -29,7 +29,7 @@ $(function(){
var offset = 0;
var duration = 0;
let no_warning = localStorage.getItem('no-warning');
// 拉取现有配置
$.get("/getsetting", function(data, status) {
console.log(data, status);
@@ -192,11 +192,34 @@ $(function(){
});
}
function do_play_music_list(listname, musicname) {
$.ajax({
type: "POST",
url: "/playmusiclist",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({did: did, listname: listname, musicname: musicname}),
success: () => {
console.log("do_play_music_list succ", listname, musicname);
},
error: () => {
console.log("do_play_music_list failed", listname, musicname);
}
});
}
$("#play_music_list").on("click", () => {
var music_list = $("#music_list").val();
var music_name = $("#music_name").val();
let cmd = "播放列表" + music_list + "|" + music_name;
sendcmd(cmd);
if (no_warning) {
do_play_music_list(music_list, music_name);
return;
}
$.get(`/musicinfo?name=${music_name}`, function(data, status) {
console.log(data);
if (data.ret == "OK") {
validHost(data.url) && do_play_music_list(music_list, music_name);
}
});
});
$("#web_play").on("click", () => {
@@ -204,7 +227,7 @@ $(function(){
$.get(`/musicinfo?name=${music_name}`, function(data, status) {
console.log(data);
if (data.ret == "OK") {
$('audio').attr('src',data.url);
validHost(data.url) && $('audio').attr('src',data.url);
}
});
});
@@ -259,17 +282,32 @@ $(function(){
$container.append($button);
}
function do_play_music(musicname, searchkey) {
$.ajax({
type: "POST",
url: "/playmusic",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({did: did, musicname: musicname, searchkey: searchkey}),
success: () => {
console.log("do_play_music succ", musicname, searchkey);
},
error: () => {
console.log("do_play_music failed", musicname, searchkey);
}
});
}
$("#play").on("click", () => {
var search_key = $("#music-name").val();
if (search_key == null) {
search_key = "";
}
var filename = $("#music-filename").val();
if (filename == null) {
filename = "";
if (filename == null || filename == "") {
filename = search_key;
}
let cmd = "播放歌曲" + search_key + "|" + filename;
sendcmd(cmd);
do_play_music(filename, search_key);
});
$("#volume").on('change', function () {
@@ -375,7 +413,7 @@ $(function(){
.catch(error => {
console.error('Error fetching data:', error);
});
}, 300));
}, 500));
// 动态显示保存文件名输入框
const musicNameSelect = document.getElementById('music-name');
@@ -435,5 +473,48 @@ $(function(){
var minutes = Math.floor(seconds / 60);
var remainingSeconds =Math.floor(seconds % 60);
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
}
$("audio").on("error", (e) => {
//如果audio标签的src为空则不做任何操作兼容安卓端的低版本webview
if ($("audio").attr("src") === "") {
return;
}
console.log('%c网页播放出现错误: ', 'color: #007acc;', e.currentTarget.error.code,e.currentTarget.error.message);
alert(e.currentTarget.error.code==4 ? "无法打开媒体文件XIAOMUSIC_HOSTNAME或端口地址错误请重新设置" : "在线播放失败,请截图反馈: "+e.currentTarget.error.message);
});
function validHost(url) {
//如果 localStorage 中有 no-warning 则直接返回true
if (no_warning) {
return true;
}
const local = location.host;
const host = new URL(url).host;
// 如果当前页面的Host与设置中的XIAOMUSIC_HOSTNAME、PORT一致, 不再提醒
if (local === host) {
localStorage.setItem('no-warning', 'true');
// 设置全局变量
no_warning = true;
return true;
}
// 如果当前页面的Host与设置中的XIAOMUSIC_HOSTNAME、PORT不一致
const validHost = document.getElementById('valid-host');
let validFlag = false;
$('#local-host').text(local);
$('#setting-host').text(host);
validHost.showModal();
//监听validHost的close事件
function _handleClose() {
console.log('%c提醒HOST不一致弹窗,用户已选择: ', 'color: #007acc;', validHost.returnValue);
if (validHost.returnValue == "no-warning") {
localStorage.setItem('no-warning', 'true');
no_warning = true;
validFlag = true;
}
validHost.removeEventListener('close', _handleClose)
}
validHost.addEventListener('close', _handleClose)
return validFlag;
}
});

View File

@@ -6,9 +6,9 @@
<meta name="viewport" content="width=device-width">
<title>Debug For XiaoMusic</title>
<link rel="stylesheet" type="text/css" href="./style.css?version=1732797036">
<link rel="stylesheet" type="text/css" href="./style.css?version=1733278946">
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script src="./jquery-3.7.1.min.js?version=1732797036"></script>
<script src="./jquery-3.7.1.min.js?version=1733278946"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>歌曲下载工具</title>
<link rel="stylesheet" type="text/css" href="./style.css?version=1732797036">
<script src="./jquery-3.7.1.min.js?version=1732797036"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1733278946">
<script src="./jquery-3.7.1.min.js?version=1733278946"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>

View File

@@ -4,9 +4,9 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="./jquery-3.7.1.min.js?version=1732797036"></script>
<script src="./app.js?version=1732797036"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1732797036">
<script src="./jquery-3.7.1.min.js?version=1733278946"></script>
<script src="./app.js?version=1733278946"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1733278946">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
@@ -94,5 +94,17 @@ var vConsole = new window.VConsole();
<footer>
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
</footer>
<dialog id="valid-host">
<form method="dialog">
<p>当前页面的HOST与设置中的HOST不一致请检查是否设置错误</p>
<p>当前HOST: <span id="local-host"></span></p>
<p>设置中的HOST: <span id="setting-host"></span></p>
<div class="btn-list">
<a href="./setting.html" target="_blank">立即修改</a>
<button value="no-warning" type="submit">继续并不再显示</button>
<button value="cancle" type="submit">取消</button>
</div>
</form>
</dialog>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width">
<title>M3U to JSON Converter</title>
<link rel="stylesheet" type="text/css" href="./style.css?version=1732797036">
<link rel="stylesheet" type="text/css" href="./style.css?version=1733278946">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>

View File

@@ -4,9 +4,9 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="./jquery-3.7.1.min.js?version=1732797036"></script>
<script src="./setting.js?version=1732797036"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1732797036">
<script src="./jquery-3.7.1.min.js?version=1733278946"></script>
<script src="./setting.js?version=1733278946"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1733278946">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
@@ -43,13 +43,13 @@ var vConsole = new window.VConsole();
<div id="setting">
<div class="rows">
<label for="account">*小米账号:</label>
<input id="account" type="text" placeholder="填写小米登录账号"></input>
<input id="account" type="text" placeholder="填写小米登录账号" />
<label for="password">*小米密码:</label>
<input id="password" type="password" placeholder="填写小米登录密码"></input>
<input id="password" type="password" placeholder="填写小米登录密码" />
<label for="hostname">*XIAOMUSIC_HOSTNAME(IP或域名):</label>
<input id="hostname" type="text"></input>
<input id="hostname" type="text" />
</div>
<hr>
<div class="rows">
@@ -59,38 +59,44 @@ var vConsole = new window.VConsole();
<option value="false">false</option>
</select>
<label for="port">监听端口(修改后需要重启):</label>
<input id="port" type="number" value="8090" />
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0" />
<label for="group_list">设备分组配置:<a href="https://github.com/hanxi/xiaomusic/issues/65#issuecomment-2215736529" target="_blank">文档</a></label>
<input id="group_list" type="text" placeholder="did1:组名1,did2:组名1,did3:组名2"></input>
<input id="group_list" type="text" placeholder="did1:组名1,did2:组名1,did3:组名2" />
<label for="music_path">音乐目录:</label>
<input id="music_path" type="text" value="music"></input>
<input id="music_path" type="text" value="music" />
<label for="download_path">音乐下载目录(必须是music的子目录):</label>
<input id="download_path" type="text" value='music/download'></input>
<input id="download_path" type="text" value='music/download' />
<label for="conf_path">配置文件目录:</label>
<input id="conf_path" type="text"></input>
<input id="conf_path" type="text" />
<label for="cache_dir">缓存文件目录:</label>
<input id="cache_dir" type="text"></input>
<input id="cache_dir" type="text" />
<label for="temp_path">临时文件目录:</label>
<input id="temp_path" type="text" value="music/tmp"></input>
<input id="temp_path" type="text" value="music/tmp" />
<label for="ffmpeg_location">ffmpeg路径:</label>
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin"></input>
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin" />
<label for="log_file">日志路径:</label>
<input id="log_file" type="text" value="/tmp/xiaomusic.txt"></input>
<input id="log_file" type="text" value="xiaomusic.log.txt" />
<label for="active_cmd">允许唤醒的命令:</label>
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop"></input>
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop" />
<label for="exclude_dirs">忽略目录(逗号分割):</label>
<input id="exclude_dirs" type="text" value="@eaDir,tmp"></input>
<input id="exclude_dirs" type="text" value="@eaDir,tmp" />
<label for="music_path_depth">目录深度:</label>
<input id="music_path_depth" type="number" value="10"></input>
<input id="music_path_depth" type="number" value="10" />
<label for="search_prefix">XIAOMUSIC_SEARCH(歌曲下载方式):</label>
<select id="search_prefix">
@@ -99,7 +105,7 @@ var vConsole = new window.VConsole();
</select>
<label for="proxy">XIAOMUSIC_PROXY(ytsearch需要):</label>
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080"></input>
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080" />
<label for="remove_id3tag">去除MP3 ID3v2和填充:</label>
<select id="remove_id3tag">
@@ -114,7 +120,7 @@ var vConsole = new window.VConsole();
</select>
<label for="miio_tts_command">MiIO tts 指令(解决部分型号没有提示音的问题):</label>
<input id="miio_tts_command" type="text" placeholder="如5 或者 5-3"></input>
<input id="miio_tts_command" type="text" placeholder="如5 或者 5-3" />
<label for="disable_httpauth">关闭控制台密码验证:</label>
<select id="disable_httpauth">
@@ -122,9 +128,9 @@ var vConsole = new window.VConsole();
<option value="false">false</option>
</select>
<label for="httpauth_username">控制台账户:</label>
<input id="httpauth_username" type="text" value=""></input>
<input id="httpauth_username" type="text" value="" />
<label for="httpauth_password">控制台密码:</label>
<input id="httpauth_password" type="password" value=""></input>
<input id="httpauth_password" type="password" value="" />
<label for="disable_download">关闭下载功能:</label>
<select id="disable_download">
@@ -133,12 +139,12 @@ var vConsole = new window.VConsole();
</select>
<label for="use_music_audio_id">触屏版显示歌曲ID:</label>
<input id="use_music_audio_id" type="text" value="1582971365183456177"></input>
<input id="use_music_audio_id" type="text" value="1582971365183456177" />
<label for="use_music_id">触屏版显示歌曲分段ID:</label>
<input id="use_music_id" type="text" value="355454500"></input>
<input id="use_music_id" type="text" value="355454500" />
<label for="fuzzy_match_cutoff">模糊匹配阈值(0.1~0.9):</label>
<input id="fuzzy_match_cutoff" type="number" value="0.6"></input>
<input id="fuzzy_match_cutoff" type="number" value="0.6" />
<label for="enable_fuzzy_match">开启模糊搜索:</label>
<select id="enable_fuzzy_match">
@@ -158,39 +164,33 @@ var vConsole = new window.VConsole();
<option value="false" selected>false</option>
</select>
<label for="port">监听端口(修改后需要重启):</label>
<input id="port" type="number" value="8090"></input>
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0"></input>
<label for="pull_ask_sec">获取对话记录间隔(秒):</label>
<input id="pull_ask_sec" type="number" value="1"></input>
<input id="pull_ask_sec" type="number" value="1" />
<label for="delay_sec">下一首歌延迟播放秒数:</label>
<input id="delay_sec" type="number" value="3"></input>
<input id="delay_sec" type="number" value="3" />
<label for="stop_tts_msg">停止提示音:</label>
<input id="stop_tts_msg" type="text" value="收到,再见"></input>
<input id="stop_tts_msg" type="text" value="收到,再见" />
<label for="play_type_one_tts_msg">单曲循环提示音:</label>
<input id="play_type_one_tts_msg" type="text" value="已经设置为单曲循环"></input>
<input id="play_type_one_tts_msg" type="text" value="已经设置为单曲循环" />
<label for="play_type_all_tts_msg">全部循环提示音:</label>
<input id="play_type_all_tts_msg" type="text" value="已经设置为全部循环"></input>
<input id="play_type_all_tts_msg" type="text" value="已经设置为全部循环" />
<label for="play_type_rnd_tts_msg">随机播放提示音:</label>
<input id="play_type_rnd_tts_msg" type="text" value="已经设置为随机播放"></input>
<input id="play_type_rnd_tts_msg" type="text" value="已经设置为随机播放" />
<label for="play_type_sin_tts_msg">单曲播放提示音:</label>
<input id="play_type_sin_tts_msg" type="text" value="已经设置为单曲播放"></input>
<input id="play_type_sin_tts_msg" type="text" value="已经设置为单曲播放" />
<label for="play_type_seq_tts_msg">顺序播放提示音:</label>
<input id="play_type_seq_tts_msg" type="text" value="已经设置为顺序播放"></input>
<input id="play_type_seq_tts_msg" type="text" value="已经设置为顺序播放" />
<label for="keywords_playlocal">播放本地歌曲口令:</label>
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲"></input>
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲" />
<label for="keywords_play">播放歌曲口令:</label>
<input id="keywords_play" type="text" value="播放歌曲,放歌曲"></input>
<input id="keywords_play" type="text" value="播放歌曲,放歌曲" />
<label for="keywords_playlist">播放列表口令:</label>
<input id="keywords_playlist" type="text" value="播放列表,播放歌单"></input>
<input id="keywords_playlist" type="text" value="播放列表,播放歌单" />
<label for="keywords_stop">停止口令:</label>
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放"></input>
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放" />
<label for="enable_yt_dlp_cookies">启用yt-dlp-cookies(需要先上传yt-dlp-cookies.txt文件):</label>
<select id="enable_yt_dlp_cookies">
@@ -204,8 +204,11 @@ var vConsole = new window.VConsole();
<option value="false" selected>false</option>
</select>
<label for="recently_added_playlist_len">最近新增的歌曲数量:</label>
<input id="recently_added_playlist_len" type="number" value="50" />
<label for="music_list_url">歌单地址:</label>
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json"></input>
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json" />
<label for="music_list_json">歌单内容:<a href="https://github.com/hanxi/xiaomusic/issues/78" target="_blank">格式文档</a></label>
<textarea id="music_list_json" type="text"></textarea>

View File

@@ -16,13 +16,19 @@ $(function(){
});
};
function updateCheckbox(selector, mi_did, device_list) {
function updateCheckbox(selector, mi_did, device_list,accountPassValid) {
// 清除现有的内容
$(selector).empty();
// 将 mi_did 字符串通过逗号分割转换为数组,以便于判断默认选中项
var selected_dids = mi_did.split(',');
//如果device_list为空则可能是未设置小米账号密码或者已设置密码但是没有过小米验证此处需要提示用户
if (device_list.length == 0) {
const loginTips = accountPassValid ? `<div class="login-tips">未发现可用的小爱设备,请检查账号密码是否输错,并关闭加速代理或在<a href="https://www.mi.com">小米官网</a>登陆过人脸或滑块验证。如仍未解决。请根据<a href="https://github.com/hanxi/xiaomusic/issues/99">FAQ</a>的内容解决问题。</div>` : `<div class="login-tips">未发现可用的小爱设备,请先在下面的输入框中设置小米的<b>账号、密码</b></div>`;
$(selector).append(loginTips);
return;
}
$.each(device_list, function(index, device) {
var did = device.miotDID;
var hardware = device.hardware;
@@ -64,7 +70,8 @@ $(function(){
// 拉取现有配置
$.get("/getsetting?need_device_list=true", function(data, status) {
console.log(data, status);
updateCheckbox("#mi_did", data.mi_did, data.device_list);
const accountPassValid = data.account && data.password;
updateCheckbox("#mi_did", data.mi_did, data.device_list, accountPassValid);
// 初始化显示
for (const key in data) {

View File

@@ -21,7 +21,6 @@ button:active, .button:active {
}
label {
margin-left: 10px;
width: 300px;
}
input,select {
margin-left: 5%;
@@ -100,3 +99,67 @@ footer {
animation: blink 1s infinite;
}
.login-tips {
color: red;
font-size: 12px;
margin-left: 10px;
}
.login-tips a {
color: rgb(9, 105, 218);
text-decoration: underline;
}
#valid-host {
padding: 20px;
border: none;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
#valid-host::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
#valid-host form input {
width: fit-content;
margin: 0;
height: fit-content;
}
#valid-host p {
word-break: break-all;
}
#valid-host p span {
color: red;
}
#valid-host a, #valid-host a:visited {
color: rgb(9, 105, 218);;
text-decoration: underline;
display: flex;
align-items: center;
}
#valid-host a:hover {
color: rgb(9, 95, 198);
}
#valid-host .btn-list {
display: flex;
justify-content: center;
margin-top: 20px;
}
#valid-host .btn-list button {
width: fit-content;
min-width: 60px;
height: 40px;
border: none;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
border-radius: 10px;
background-color: #008CBA;
}
#valid-host .btn-list button:hover {
font-weight:bold;
background-color: #007CBA;
}

View File

@@ -43,6 +43,10 @@
<a href="/static/default/index.html" class="href">默认主题</a>
<a href="/static/pure/index.html" class="href">Pure主题</a>
<a href="/static/xplayer/index.html" class="href">XMusicPlayer</a>
<div class="weapp">
<a href="https://github.com/F-loat/xiaoplayer" class="href" target="_blank">微信小程序</a>
<iframe width="240px" height="240px" src="/static/weapp/qrcode.html"></iframe>
</div>
<a href="https://afdian.com/a/imhanxi" target="_blank">爱发电</a>
<a href="https://github.com/hanxi/xiaomusic" target="_blank">GitHub</a>
</div>
@@ -65,6 +69,9 @@
.options a{ color: #a2a9af; text-decoration: none; font-size: 1.1em; position: relative; display: inline; margin: 10px auto;}
.options a::before{ content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background-color: #ebedec; transform-origin: bottom right; transform: scaleX(0); transition: transform 0.3s ease;}
.options a:hover::before{ transform-origin: bottom left; transform: scaleX(1);} .options a:hover{ color:#ebedec;}
.weapp { position: relative; text-align: center; margin: 10px auto; }
.weapp iframe { display: none; border: none;position: absolute; top: -80px; right: 100px; }
.weapp:hover iframe { display: block; }
footer{ display: flex; justify-content: center; color: #4c5870;} footer a{ color:inherit; text-decoration: none; margin: auto 10px;}
</style>
</body>

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weapp QRCode</title>
<style>
html,
body {
height: 100%;
margin: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
#qrcode {
flex: 1;
max-width: 180px;
border-radius: 12px;
background-color: white;
padding: 8px;
}
</style>
<script src="https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.4.0/cloud.js"></script>
</head>
<body>
<img id="qrcode" src="https://assets-1251785959.cos.ap-beijing.myqcloud.com/xiaoplayer/weappcode.jpg" />
<script>
const c1 = new cloud.Cloud({
identityless: true,
resourceAppid: 'wx5931d820da6e8e50',
resourceEnv: 'cards-ahoy-3g50hglqe5f630e4'
})
c1.init().then(() => {
c1.callFunction({
name: 'qrcode',
data: {
host: location.host,
protocol: location.protocol
},
complete: (res) => {
if (res.errMsg === 'cloud.callFunction:ok' && !res.result.errCode) {
const blob = new Blob([res.result.buffer])
const url = URL.createObjectURL(blob)
document.querySelector('#qrcode').setAttribute('src', url)
}
}
})
})
</script>
</body>
</html>

View File

@@ -596,6 +596,15 @@ def _to_utf8(v):
return str(v)
def save_picture_by_base64(picture_base64_data, save_root, file_path):
try:
picture_data = base64.b64decode(picture_base64_data)
except (TypeError, ValueError) as e:
log.exception(f"Error decoding base64 data: {e}")
return None
return _save_picture(picture_data, save_root, file_path)
def _save_picture(picture_data, save_root, file_path):
# 计算文件名的哈希值
file_hash = hashlib.md5(file_path.encode("utf-8")).hexdigest()

View File

@@ -28,6 +28,7 @@ from xiaomusic.const import (
COOKIE_TEMPLATE,
GET_ASK_BY_MINA,
LATEST_ASK_API,
NEED_USE_PLAY_MUSIC_API,
PLAY_TYPE_ALL,
PLAY_TYPE_ONE,
PLAY_TYPE_RND,
@@ -50,6 +51,7 @@ from xiaomusic.utils import (
list2str,
parse_cookie_string,
parse_str_to_dict,
save_picture_by_base64,
traverse_music_directory,
try_add_access_control_param,
)
@@ -156,7 +158,7 @@ class XiaoMusic:
log_file = self.config.log_file
log_path = os.path.dirname(log_file)
if not os.path.exists(log_path):
if log_path and not os.path.exists(log_path):
os.makedirs(log_path)
if os.path.exists(log_file):
os.remove(log_file)
@@ -186,7 +188,7 @@ class XiaoMusic:
self.last_timestamp[did] = int(time.time() * 1000)
hardware = self.get_hardward(device_id)
if hardware in GET_ASK_BY_MINA or self.config.get_ask_by_mina:
if (hardware in GET_ASK_BY_MINA) or self.config.get_ask_by_mina:
tasks.append(self.get_latest_ask_by_mina(device_id))
else:
tasks.append(
@@ -463,6 +465,27 @@ class XiaoMusic:
)
return tags
# 修改标签信息
def set_music_tag(self, name, info):
if self._tag_generation_task:
self.log.info("tag 更新中,请等待")
return "Tag generation task running"
tags = copy.copy(self.all_music_tags.get(name, asdict(Metadata())))
tags["title"] = info.title
tags["artist"] = info.artist
tags["album"] = info.album
tags["year"] = info.year
tags["genre"] = info.genre
tags["lyrics"] = info.lyrics
if info.picture:
file_path = self.all_music[name]
tags["picture"] = save_picture_by_base64(
info.picture, self.config.picture_cache_path, file_path
)
self.all_music_tags[name] = tags
self.try_save_tag_cache()
return "OK"
def get_music_url(self, name):
if self.is_web_music(name):
url = self.all_music[name]
@@ -618,13 +641,15 @@ class XiaoMusic:
"全部": [], # 包含所有歌曲和所有电台
"下载": [], # 下载目录下的
"其他": [], # 主目录下的
"最近新增": [], # 按文件时间排序
}
)
# 全部,所有,自定义歌单(收藏)
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.music_list["最近新增"] = sorted(
self.all_music.keys(),
key=lambda x: os.path.getctime(self.all_music[x]),
reverse=True,
)[: self.config.recently_added_playlist_len]
# 网络歌单
try:
@@ -633,6 +658,12 @@ class XiaoMusic:
except Exception as e:
self.log.exception(f"Execption {e}")
# 全部,所有,自定义歌单(收藏)
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
]
# 文件夹歌单
for dir_name, musics in all_music_by_dir.items():
self.music_list[dir_name] = list(musics.keys())
@@ -950,14 +981,17 @@ class XiaoMusic:
parts = arg1.split("|")
list_name = parts[0]
music_name = ""
if len(parts) > 1:
music_name = parts[1]
return await self.do_play_music_list(did, list_name, music_name)
async def do_play_music_list(self, did, list_name, music_name=""):
list_name = self._find_real_music_list_name(list_name)
if list_name not in self.music_list:
await self.do_tts(did, f"播放列表{list_name}不存在")
return
music_name = ""
if len(parts) > 1:
music_name = parts[1]
await self.devices[did].play_music_list(list_name, music_name)
# 播放一个播放列表里第几个
@@ -989,9 +1023,12 @@ class XiaoMusic:
parts = arg1.split("|")
search_key = parts[0]
name = parts[1] if len(parts) > 1 else search_key
if name == "":
if not name:
name = search_key
return await self.do_play(did, name, search_key)
async def do_play(self, did, name, search_key=""):
return await self.devices[did].play(name, search_key)
# 本地播放
@@ -1260,7 +1297,11 @@ class XiaoMusicDevice:
@property
def did(self):
return self.xiaomusic.device_id_did[self.device_id]
return self.device.did
@property
def hardware(self):
return self.device.hardware
def get_cur_music(self):
return self.device.cur_music
@@ -1442,7 +1483,7 @@ class XiaoMusicDevice:
return
self.log.info(f"{name}】已经开始播放了")
await self.xiaomusic.analytics.send_play_event(name, sec)
await self.xiaomusic.analytics.send_play_event(name, sec, self.hardware)
if self.device.play_type == PLAY_TYPE_SIN:
self.log.info(f"{name}】单曲播放时不会设置下一首歌的定时器")
@@ -1686,7 +1727,9 @@ class XiaoMusicDevice:
self.log.info(
f"play_one_url continue_play device_id:{device_id} ret:{ret} url:{url} audio_id:{audio_id}"
)
elif self.config.use_music_api:
elif self.config.use_music_api or (
self.hardware in NEED_USE_PLAY_MUSIC_API
):
ret = await self.xiaomusic.mina_service.play_by_music_url(
device_id, url, audio_id=audio_id
)