mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-06 14:52:50 +08:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0db3e4cada | ||
|
|
fa21d02e1d | ||
|
|
be48fe9f54 | ||
|
|
042a91336e | ||
|
|
790a4cd808 | ||
|
|
70d029f4d1 | ||
|
|
0472ad3188 | ||
|
|
733c44d12f | ||
|
|
8c92afd09b | ||
|
|
742929b9d5 | ||
|
|
4766e08ff4 | ||
|
|
99c4296c7a | ||
|
|
0ae24f4810 | ||
|
|
77cca0d18c | ||
|
|
ddc24595df | ||
|
|
ce1bfb164c | ||
|
|
78dbba6c1f | ||
|
|
7eb0ab1e46 | ||
|
|
0a2e3cc579 | ||
|
|
75ec336285 | ||
|
|
a19d53f000 | ||
|
|
cdb6190e7a | ||
|
|
1aa9b58561 | ||
|
|
6a990f48d0 | ||
|
|
82e92a0380 | ||
|
|
ecce5c8848 | ||
|
|
87fb34e5c9 | ||
|
|
df3c4b7fa9 | ||
|
|
cb2af0ee9f | ||
|
|
8f9bba0ca3 | ||
|
|
b86e8d3196 | ||
|
|
65067346f3 | ||
|
|
865b412fb7 | ||
|
|
441fffd59e | ||
|
|
2ee7b956cf | ||
|
|
126bfa43a2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -169,3 +169,4 @@ setting.json
|
||||
.DS_Store
|
||||
cache
|
||||
tmp/
|
||||
xiaomusic.log.txt
|
||||
|
||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -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
|
||||
|
||||
31
README.md
31
README.md
@@ -6,7 +6,8 @@
|
||||
[](https://pypi.org/project/xiaomusic/)
|
||||
[](https://pypi.org/project/xiaomusic/)
|
||||
[](https://github.com/hanxi/xiaomusic/releases)
|
||||
|
||||
[](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
|
||||
[](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
86
pdm.lock
generated
@@ -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"},
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.49"
|
||||
__version__ = "0.3.55"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(","):
|
||||
|
||||
@@ -20,3 +20,10 @@ PLAY_TYPE_SEQ = 4 # 顺序播放
|
||||
GET_ASK_BY_MINA = {
|
||||
"M01",
|
||||
}
|
||||
|
||||
# 需要使用 play_musci 接口的设备型号
|
||||
NEED_USE_PLAY_MUSIC_API = {
|
||||
"X08C",
|
||||
"X08E",
|
||||
"X8F",
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
60
xiaomusic/static/weapp/qrcode.html
Normal file
60
xiaomusic/static/weapp/qrcode.html
Normal 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>
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user