mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-07 15:02:55 +08:00
Compare commits
79 Commits
alert-auto
...
v0.3.88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28ec3ef9f4 | ||
|
|
2760f830ce | ||
|
|
eb099e5aa2 | ||
|
|
cb5f13c09b | ||
|
|
3b7063edfd | ||
|
|
0a3bed40d4 | ||
|
|
da90dbad60 | ||
|
|
82f7c1f5dd | ||
|
|
4cc9dd2cbb | ||
|
|
10a24c99e4 | ||
|
|
9e40e4baeb | ||
|
|
5525712c77 | ||
|
|
a36709e838 | ||
|
|
fcfff7c090 | ||
|
|
21186d52eb | ||
|
|
53a2f3d334 | ||
|
|
4945bf554e | ||
|
|
5cb7126eec | ||
|
|
7167670a14 | ||
|
|
89cb0923ee | ||
|
|
2aae9ad0bc | ||
|
|
94ce1f95f1 | ||
|
|
444211963b | ||
|
|
e4654944cc | ||
|
|
01d8584fe0 | ||
|
|
80a7345ae7 | ||
|
|
5cf9a2a5f1 | ||
|
|
6a37e1ec70 | ||
|
|
8246ebf74d | ||
|
|
b1e2667fad | ||
|
|
06f96fe686 | ||
|
|
00a74fa1da | ||
|
|
f2bcb1b9aa | ||
|
|
93bdcc7ed8 | ||
|
|
80fe648610 | ||
|
|
d6b530582d | ||
|
|
749fa88f62 | ||
|
|
f2daaf77e2 | ||
|
|
7e96c7ff33 | ||
|
|
aea9786cd7 | ||
|
|
e0627dfe8b | ||
|
|
2f1df2709e | ||
|
|
e51f873fdf | ||
|
|
80ead7512f | ||
|
|
f64d9b2462 | ||
|
|
b3c6ca3943 | ||
|
|
001e0ede20 | ||
|
|
53f302c946 | ||
|
|
57ca7a38c4 | ||
|
|
b379cbf613 | ||
|
|
5d0e974a12 | ||
|
|
0b8af47702 | ||
|
|
85da3069ed | ||
|
|
d5eb5d5233 | ||
|
|
cf43527020 | ||
|
|
5b310c269f | ||
|
|
b547fa08da | ||
|
|
f27e5299a8 | ||
|
|
b566958843 | ||
|
|
faa3013898 | ||
|
|
06de73e8ed | ||
|
|
f4377069ad | ||
|
|
35fb9510c6 | ||
|
|
74df4f0676 | ||
|
|
2ffb30415b | ||
|
|
71dcd2e628 | ||
|
|
65ee9de60c | ||
|
|
feaab8c1f5 | ||
|
|
ca427d69ef | ||
|
|
1ac2c20604 | ||
|
|
c2cb13e61e | ||
|
|
778d8f8b9d | ||
|
|
de3365fbbb | ||
|
|
a316053d01 | ||
|
|
0bfa4e6251 | ||
|
|
6ef7d4c6c2 | ||
|
|
fff35783c2 | ||
|
|
c8fad5ba1d | ||
|
|
956a4af3c9 |
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,3 +1,61 @@
|
||||
## v0.3.88 (2025-09-16)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增歌单合并工具
|
||||
- 兼容 X6A 型号
|
||||
|
||||
## v0.3.87 (2025-09-11)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增 websocket 接口获取当前播放状态
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复本地播放失败问题
|
||||
|
||||
## v0.3.86 (2025-09-08)
|
||||
|
||||
### Feat
|
||||
|
||||
- LX音源支持http_proxy
|
||||
- 支持LX歌单
|
||||
- 代理播放模式使用原始地址获取歌曲时长
|
||||
- 网络歌曲支持使用代理的方式播放
|
||||
- 新增代理播放链接功能 see: #525
|
||||
|
||||
## v0.3.85 (2025-08-08)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复延迟关机按钮失效问题
|
||||
|
||||
## v0.3.84 (2025-08-03)
|
||||
|
||||
### Feat
|
||||
|
||||
- 下一首歌延迟播放秒数支持负数,用于解决播放下一首时会播放上一首的开头几秒的问题
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复谷歌字体问题
|
||||
- 文件监控: 忽略非文件创建、删除和移动事件 (#514)
|
||||
- 修复中文定时关机无法识别的BUG (#510)
|
||||
- 修复日志文件删除失败的问题
|
||||
|
||||
## v0.3.83 (2025-06-12)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增开关控制是否开始谷歌统计 see #473
|
||||
- 支持b站合集和收藏下载 (#487)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复安全问题
|
||||
- 修复安全问题
|
||||
|
||||
## v0.3.82 (2025-05-30)
|
||||
|
||||
### Fix
|
||||
|
||||
@@ -9,8 +9,10 @@ RUN apk add --no-cache bash\
|
||||
supervisor \
|
||||
vim \
|
||||
libc6-compat \
|
||||
ffmpeg \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY install_dependencies.sh .
|
||||
RUN bash install_dependencies.sh
|
||||
RUN mkdir -p /app/ffmpeg/bin \
|
||||
&& ln -s /usr/bin/ffmpeg /app/ffmpeg/bin/ffmpeg \
|
||||
&& ln -s /usr/bin/ffprobe /app/ffmpeg/bin/ffprobe
|
||||
|
||||
11
README.md
11
README.md
@@ -81,6 +81,13 @@ services:
|
||||
|
||||
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
|
||||
|
||||
> [!TIP]
|
||||
> 作者的另一个适用于 NAS 上安装的开源工具: <https://github.com/hanxi/tiny-nav>
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 喜欢听书的可以配合这个工具使用 <https://github.com/hanxi/epub2mp3>
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> - 🔥【广告:可用于安装 frp 实现内网穿透】
|
||||
@@ -96,7 +103,7 @@ services:
|
||||
|
||||
> [!TIP]
|
||||
> - 免费主机
|
||||
> - <a href="https://dartnode.com"><img src="https://dartnode.com/branding/DN-Open-Source-sm.png" alt="Powered by DartNode - Free VPS for Open Source" width="320"></a>
|
||||
> - <a href="https://dartnode.com?aff=SnappyPigeon570"><img src="https://dartnode.com/branding/DN-Open-Source-sm.png" alt="Powered by DartNode - Free VPS for Open Source" width="320"></a>
|
||||
|
||||
|
||||
### 🤐 支持语音口令
|
||||
@@ -114,7 +121,7 @@ services:
|
||||
- 【加入收藏】,把当前播放的歌曲加入收藏歌单。
|
||||
- 【取消收藏】,把当前播放的歌曲从收藏歌单里移除。
|
||||
- 【播放列表收藏】,这个用于播放收藏歌单。
|
||||
- 【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。
|
||||
- ~【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。~
|
||||
- 【播放列表第几个+列表名】,具体见: <https://github.com/hanxi/xiaomusic/issues/158>
|
||||
- 【搜索播放+关键词】,会搜索关键词作为临时搜索列表播放,比如说【搜索播放林俊杰】,会播放所有林俊杰的歌。
|
||||
- 【本地搜索播放+关键词】,跟搜索播放的区别是本地找不到也不会去下载。
|
||||
|
||||
@@ -112,5 +112,114 @@ docker pull dockerhub.anzu.vip/hanxi/xiaomusic:latest
|
||||
|
||||
不确定,但是我访问谷歌啥的都正常啊
|
||||
|
||||
---
|
||||
|
||||
### 评论 9 - 22555642
|
||||
|
||||
你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
|
||||
2025/07/18 17:15:55 | stdout | [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
-- | -- | --
|
||||
2025/07/18 17:15:55 | stdout | [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:55 | stdout | ==> /app/xiaomusic.log.txt <==
|
||||
2025/07/18 17:15:55 | stdout |
|
||||
2025/07/18 17:15:55 | stderr | tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
2025/07/18 17:15:52 | stdout | 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
|
||||
2025/07/18 17:15:48 | stdout | 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
2025/07/18 17:15:48 | stdout | ==> /app/supervisord.log <==
|
||||
2025/07/18 17:15:48 | stdout |
|
||||
2025/07/18 17:15:43 | stderr | tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
2025/07/18 17:15:43 | stdout | [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:43 | stdout | [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:43 | stdout | ==> /app/xiaomusic.log.txt <==
|
||||
2025/07/18 17:15:43 | stdout |
|
||||
2025/07/18 17:15:40 | stdout | 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
|
||||
2025/07/18 17:15:37 | stdout | 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
2025/07/18 17:15:37 | stdout | ==> /app/supervisord.log <==
|
||||
2025/07/18 17:15:37 | stdout |
|
||||
2025/07/18 17:15:33 | stdout | [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:33 | stdout | [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:33 | stdout | ==> /app/xiaomusic.log.txt <==
|
||||
|
||||
---
|
||||
|
||||
### 评论 10 - hanxi
|
||||
|
||||
> 你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
|
||||
>
|
||||
> 2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
> 2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
> 2025/07/18 17:15:55 stdout ==> /app/xiaomusic.log.txt <==
|
||||
> 2025/07/18 17:15:55 stdout
|
||||
> 2025/07/18 17:15:55 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
> 2025/07/18 17:15:52 stdout 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
|
||||
> 2025/07/18 17:15:48 stdout 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
> 2025/07/18 17:15:48 stdout ==> /app/supervisord.log <==
|
||||
> 2025/07/18 17:15:48 stdout
|
||||
> 2025/07/18 17:15:43 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
> 2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
> 2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
> 2025/07/18 17:15:43 stdout ==> /app/xiaomusic.log.txt <==
|
||||
> 2025/07/18 17:15:43 stdout
|
||||
> 2025/07/18 17:15:40 stdout 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
|
||||
> 2025/07/18 17:15:37 stdout 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
> 2025/07/18 17:15:37 stdout ==> /app/supervisord.log <==
|
||||
> 2025/07/18 17:15:37 stdout
|
||||
> 2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
> 2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
> 2025/07/18 17:15:33 stdout ==> /app/xiaomusic.log.txt <==
|
||||
|
||||
看不出来为啥
|
||||
|
||||
---
|
||||
|
||||
### 评论 11 - 22555642
|
||||
|
||||
我把日志发给您,您可以帮我看看嘛?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
------------------ 原始邮件 ------------------
|
||||
发件人: "hanxi/xiaomusic" ***@***.***>;
|
||||
发送时间: 2025年7月19日(星期六) 凌晨1:31
|
||||
***@***.***>;
|
||||
***@***.******@***.***>;
|
||||
主题: Re: [hanxi/xiaomusic] 群晖docker安装 xiaomusic (Issue #101)
|
||||
|
||||
|
||||
|
||||
hanxi left a comment (hanxi/xiaomusic#101)
|
||||
|
||||
你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
|
||||
|
||||
2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:55 stdout ==> /app/xiaomusic.log.txt <==
|
||||
2025/07/18 17:15:55 stdout
|
||||
2025/07/18 17:15:55 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
2025/07/18 17:15:52 stdout 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
|
||||
2025/07/18 17:15:48 stdout 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
2025/07/18 17:15:48 stdout ==> /app/supervisord.log <==
|
||||
2025/07/18 17:15:48 stdout
|
||||
2025/07/18 17:15:43 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:43 stdout ==> /app/xiaomusic.log.txt <==
|
||||
2025/07/18 17:15:43 stdout
|
||||
2025/07/18 17:15:40 stdout 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
|
||||
2025/07/18 17:15:37 stdout 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
2025/07/18 17:15:37 stdout ==> /app/supervisord.log <==
|
||||
2025/07/18 17:15:37 stdout
|
||||
2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:33 stdout ==> /app/xiaomusic.log.txt <==
|
||||
|
||||
看不出来为啥
|
||||
|
||||
—
|
||||
Reply to this email directly, view it on GitHub, or unsubscribe.
|
||||
You are receiving this because you commented.Message ID: ***@***.***>
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/101)
|
||||
|
||||
@@ -461,5 +461,117 @@ config.json 是基本废弃了,作为一个初始化配置存在。
|
||||
1. 目前会记录最后一次播放的歌曲,正常说播放歌曲,不带歌曲名字应该就会重头播放的。
|
||||
2. 对话模式有些设备不支持,实现也比较复杂,xiaogpt那些项目是支持对话模式的,我就懒得支持了。
|
||||
|
||||
---
|
||||
|
||||
### 评论 23 - thefreezoo
|
||||
|
||||
我想写个可以读小说的,现在就是可以读不能关闭了,只能一次读完,我本想重新发送一条关闭命令,上一个自定义命令没有结束,似乎不能接受新的命令,包括关机,有什么语音控制接口可以关闭这个运行中的任务吗
|
||||
|
||||
---
|
||||
|
||||
### 评论 24 - hanxi
|
||||
|
||||
> 我想写个可以读小说的,现在就是可以读不能关闭了,只能一次读完,我本想重新发送一条关闭命令,上一个自定义命令没有结束,似乎不能接受新的命令,包括关机,有什么语音控制接口可以关闭这个运行中的任务吗
|
||||
|
||||
可以看看你是怎么写的?估计得用 asyncio 异步的写法。
|
||||
|
||||
---
|
||||
|
||||
### 评论 25 - thefreezoo
|
||||
|
||||
> > 我想写个可以读小说的,现在就是可以读不能关闭了,只能一次读完,我本想重新发送一条关闭命令,上一个自定义命令没有结束,似乎不能接受新的命令,包括关机,有什么语音控制接口可以关闭这个运行中的任务吗
|
||||
>
|
||||
> 可以看看你是怎么写的?估计得用 asyncio 异步的写法。
|
||||
|
||||
python我用的不很熟,基本逻辑都是用服务器实现的,好像python不能直接这样开启异步线程:import json
|
||||
import threading
|
||||
import requests
|
||||
import asyncio
|
||||
|
||||
async def getbook(xiaomusic):
|
||||
# global log, xiaomusic
|
||||
offset = '0'
|
||||
did = xiaomusic._cur_did
|
||||
|
||||
requests.get("http://192.168.1.16:8001/note/off?off=N",timeout=100)
|
||||
while True:
|
||||
url="http://192.168.1.16:8001/note/list?name=test&offset=" + offset
|
||||
try:
|
||||
response=requests.get(url,timeout=100)
|
||||
if response.status_code==200:
|
||||
print(response.text)
|
||||
res = json.loads(response.text)
|
||||
await xiaomusic.do_tts(did, res['data'])
|
||||
offset=str(res["offset"])
|
||||
if res['off'] == 1:
|
||||
break
|
||||
else:
|
||||
print(f"no 200")
|
||||
break
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"{e}")
|
||||
break
|
||||
|
||||
def read():
|
||||
global log, xiaomusic
|
||||
loop=asyncio.get_event_loop()
|
||||
loop.run_until_complete(getbook(xiaomusic))
|
||||
|
||||
---
|
||||
|
||||
### 评论 26 - hanxi
|
||||
|
||||
@thefreezoo 你用了个死循环,把整个服务卡住了。你可以在死循环里加个 asyncio.sleep ,然后再写一个自定义口令杀掉你的这个 task 。
|
||||
|
||||
---
|
||||
|
||||
### 评论 27 - thefreezoo
|
||||
|
||||
> [@thefreezoo](https://github.com/thefreezoo) 你用了个死循环,把整个服务卡住了。你可以在死循环里加个 asyncio.sleep ,然后再写一个自定义口令杀掉你的这个 task 。
|
||||
似乎还是不行,不能识别其他口令
|
||||
import json
|
||||
import threading
|
||||
import requests
|
||||
import asyncio
|
||||
|
||||
async def read():
|
||||
global log, xiaomusic
|
||||
offset = '0'
|
||||
did = xiaomusic._cur_did
|
||||
|
||||
requests.get("http://192.168.1.16:8001/note/off?off=N",timeout=100)
|
||||
while True:
|
||||
url="http://192.168.1.16:8001/note/list?name=test&offset=" + offset
|
||||
try:
|
||||
response=requests.get(url,timeout=100)
|
||||
if response.status_code==200:
|
||||
print(response.text)
|
||||
res = json.loads(response.text)
|
||||
await xiaomusic.do_tts(did, res['data'])
|
||||
offset=str(res["offset"])
|
||||
if res['off'] == 1:
|
||||
break
|
||||
else:
|
||||
print(f"no 200")
|
||||
break
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"{e}")
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
---
|
||||
|
||||
### 评论 28 - hanxi
|
||||
|
||||
要不你问问ai吧,你缺少一个自定义指令干掉你这个死循环。
|
||||
|
||||
---
|
||||
|
||||
### 评论 29 - thefreezoo
|
||||
|
||||
> 要不你问问ai吧,你缺少一个自定义指令干掉你这个死循环。
|
||||
|
||||
插件是同步调用的,需要把插件包装成任务放回当前事件循环,我把plugins.py异步调用那里直接改成在事件循环中执行,然后放回running_task,stop的时候在终止任务,这样就可以用关机命令了
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/105)
|
||||
|
||||
@@ -4,7 +4,7 @@ title: yt-dlp cookies 文件上传功能
|
||||
|
||||
# yt-dlp cookies 文件上传功能
|
||||
|
||||
此功能用于解决 yt-dlp 下载资源失败时使用,比如 ip 被 B站或者 youtube 加入黑名单后才需要使用。
|
||||
此功能用于解决 yt-dlp 下载资源失败时使用,比如 **ip 被 B站或者 youtube 加入黑名单**后才需要使用。
|
||||
|
||||
上传的文件用于 yt-dlp 的 `--cookies` 参数。
|
||||
```
|
||||
|
||||
@@ -249,5 +249,19 @@ https://tutu.to/image/1.N0FHK
|
||||
|
||||
估计是网络问题
|
||||
|
||||
---
|
||||
|
||||
### 评论 26 - s493321320
|
||||
|
||||
有办法添加到homeassistant里吗?
|
||||
|
||||
---
|
||||
|
||||
### 评论 27 - Chill-26
|
||||
|
||||
> 按照飞牛的教程,部署成功了,一直没有设备显示出来,然后我也按照相应的问题集去处理:关闭本地代理。 如果是nas运行的,网络由bridge改为host。 米家app重新登陆。 mi.com官网重新登陆。 但是还是没有办法显示设备出来,请问到底是什么原因?最新的37版本。
|
||||
|
||||
同样问题,但我重新使用 [视频教程-群晖1]进行部署就正常了
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/211)
|
||||
|
||||
@@ -260,5 +260,25 @@ an'zh
|
||||
|
||||
支持服务器部署的,你需要在服务器上装个浏览器登陆过风控。
|
||||
|
||||
---
|
||||
|
||||
### 评论 16 - Lunder-R
|
||||
|
||||
> > > <img alt="截屏2024-12-05 02 23 53" width="660" src="https://private-user-images.githubusercontent.com/20666294/392519509-b9d26de9-3dcf-4e65-9460-36603735c887.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk1MDktYjlkMjZkZTktM2RjZi00ZTY1LTk0NjAtMzY2MDM3MzVjODg3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFhN2I4NzQyYjA4YzRiOTk4OTU3NmVkNjU2MjM1ODhjMmNlOWU0YTg5OTFlMzA1NTcxMTA5OTk1YjgwNThhOTgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.yrC-sn2T6PKwBJb_mal1T2yxcSz008Hb7KWVmOe6zaA"> <img alt="截屏2024-12-05 02 24 49" width="780" src="https://private-user-images.githubusercontent.com/20666294/392519852-6a204cdb-bb10-4f35-822d-613aeed0fae0.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk4NTItNmEyMDRjZGItYmIxMC00ZjM1LTgyMmQtNjEzYWVlZDBmYWUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE4MzlhODM1NDY4ZjFmN2I2Y2JkZWU5ZGFkNTNjYTNmZDg2OTU3YzA0MjRkMDA2MzRmOTk2ZGE3NmE2NjViZmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0._I8Rll2a96i-cx6WBvEwOclNInfOmZkD5HmcorzT-KI">
|
||||
> > > > > 确实是变了。192.168.31.143是我电脑的ip。hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主dhcp都绑定了。大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
|
||||
> > > >
|
||||
> > > >
|
||||
> > > > 容器的网络模式改成桥试用呢没解决的话你加群明天再详聊吧
|
||||
> > >
|
||||
> > >
|
||||
> > > 辛苦了,今天晚上还在回复。我一直用的桥。大佬,群号多少,不行我明天群里问吧。
|
||||
> >
|
||||
> >
|
||||
> > [自述](https://github.com/hanxi/xiaomusic?tab=readme-ov-file#-%E8%AE%A8%E8%AE%BA%E5%8C%BA)
|
||||
>
|
||||
> 自查解决。问题是已账号问题。绑定设备的一定是创建者,不能是管理员。
|
||||
|
||||
能不能分享一下解决大概过程,我也是这种情况,谢谢了
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/297)
|
||||
|
||||
@@ -15,5 +15,19 @@ title: 同步网易云歌单
|
||||
|
||||
什么时候可以同步apple music歌单就真好了
|
||||
|
||||
---
|
||||
|
||||
### 评论 2 - fxchby
|
||||
|
||||
试了一下,似乎用不了
|
||||
|
||||
---
|
||||
|
||||
### 评论 3 - qiujie8092916
|
||||
|
||||
> 试了一下,似乎用不了
|
||||
|
||||
哪里有问题
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/312)
|
||||
|
||||
@@ -120,5 +120,36 @@ EOF
|
||||
正常现象。可以把延迟设置为0试试。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 评论 5 - worrywast
|
||||
|
||||
> > 为啥我的播放完一首音乐之后,还会播放该歌曲开头一点点才会放下一首呢?
|
||||
>
|
||||
> 正常现象。可以把延迟设置为0试试。
|
||||
|
||||
是这个选项吗:“下一首歌延迟播放秒数:”
|
||||
我设置为0,也会有这个现象,只是没有之前播放开头那么多
|
||||
|
||||
---
|
||||
|
||||
### 评论 6 - hanxi
|
||||
|
||||
@worrywast 等下个版本我优化一下,允许设置成负数吧。
|
||||
|
||||
---
|
||||
|
||||
### 评论 7 - worrywast
|
||||
|
||||
> [@worrywast](https://github.com/worrywast) 等下个版本我优化一下,允许设置成负数吧。
|
||||
|
||||
辛苦大佬~
|
||||
|
||||
---
|
||||
|
||||
### 评论 8 - hanxi
|
||||
|
||||
@worrywast 现在就是可以填负数的,你可以试试看。
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/360)
|
||||
|
||||
@@ -41,8 +41,8 @@ python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
|
||||
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
|
||||
},
|
||||
{
|
||||
"name":"歌名4",
|
||||
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
|
||||
"name":"花海",
|
||||
"url":"http://192.168.2.5:58090/proxy?urlb64=aHR0cHM6Ly93cy5zdHJlYW0ucXFtdXNpYy5xcS5jb20vTTgwMDAwM29rV3ZvMXFadTljLm1wMz9mcm9tdGFnPTAmZ3VpZD1mZmZmZmZmZmM4MWU2ZjVhZmZmZmZmZmZlZGZmZWI3ZiZ1aW49MzMxMjkzOTI1NCZ2a2V5PTc1MURBOEQ5RkRFNTkwOEQ5MjVCNUZFMTNBODhEREUyQkI3MzZGNDQ3NDU4MTA1OTk2Q0Q3QTEyMTQ2ODFGRUZERjNBQURDMkY0OTY2NjJEMDM1OUVDRkE0RjQwMkM2M0RDOTk4NzdDOENGMkM3OUJfX3YyMWUyYTE3MzM="
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -51,6 +51,10 @@ python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
|
||||
|
||||
这里分享一个让 chatgpt 写 python 脚本来生成歌单的例子 <https://chatgpt.com/share/6751c019-74c0-800a-a978-a20c636d4464> 。
|
||||
|
||||
## 代理访问链接
|
||||
|
||||
上面的 <http://192.168.2.5:58090/proxy?urlb64=aHR0cHM6Ly93cy5zdHJlYW0ucXFtdXNpYy5xcS5jb20vTTgwMDAwM29rV3ZvMXFadTljLm1wMz9mcm9tdGFnPTAmZ3VpZD1mZmZmZmZmZmM4MWU2ZjVhZmZmZmZmZmZlZGZmZWI3ZiZ1aW49MzMxMjkzOTI1NCZ2a2V5PTc1MURBOEQ5RkRFNTkwOEQ5MjVCNUZFMTNBODhEREUyQkI3MzZGNDQ3NDU4MTA1OTk2Q0Q3QTEyMTQ2ODFGRUZERjNBQURDMkY0OTY2NjJEMDM1OUVDRkE0RjQwMkM2M0RDOTk4NzdDOENGMkM3OUJfX3YyMWUyYTE3MzM=> 是经过代理播放的链接。可以通过其他的工具来生成。其中 <http://192.168.2.5:58090/> 是自己的 xiaomusic 地址。
|
||||
|
||||
## 评论
|
||||
|
||||
|
||||
@@ -72,196 +76,13 @@ python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
|
||||
|
||||
---
|
||||
|
||||
### 评论 3 - lazybabyz
|
||||
|
||||
按照教程设置后 播放列表选择m3u电台 再选择播放列表 歌曲,结果显示播放中 不断在转台
|
||||
stdout: [08:58:02] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/index.html HTTP/1.1" 304
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /getsetting HTTP/1.1" 200
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getversion HTTP/1.1" 200
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /musiclist HTTP/1.1" 200
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:04] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /static/default/curplaylist?did=566731712 HTTP/1.1" 404
|
||||
stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24022 - "GET /getvolume?did=566731712 HTTP/1.1" 500
|
||||
stderr: [08:58:05] [0.3.37] [ERROR] Exception in ASGI application
|
||||
stderr: Traceback (most recent call last):
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi
|
||||
stderr: result = await app( # type: ignore[func-returns-value]
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in __call__
|
||||
stderr: return await self.app(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
|
||||
stderr: await super().__call__(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in __call__
|
||||
stderr: await self.middleware_stack(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in __call__
|
||||
stderr: raise exc
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in __call__
|
||||
stderr: await self.app(scope, receive, _send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in __call__
|
||||
stderr: await self.app(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
|
||||
stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app
|
||||
stderr: raise exc
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app
|
||||
stderr: await app(scope, receive, sender)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in __call__
|
||||
stderr: await self.middleware_stack(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app
|
||||
stderr: await route.handle(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle
|
||||
stderr: await self.app(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app
|
||||
stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app
|
||||
stderr: raise exc
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app
|
||||
stderr: await app(scope, receive, sender)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app
|
||||
stderr: response = await f(request)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app
|
||||
stderr: raw_response = await run_endpoint_function(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function
|
||||
stderr: return await dependant.call(**values)
|
||||
stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume
|
||||
stderr: volume = await xiaomusic.get_volume(did=did)
|
||||
stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume
|
||||
stderr: return await self.devices[did].get_volume()
|
||||
stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume
|
||||
stderr: playing_info = await self.xiaomusic.mina_service.player_get_status(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status
|
||||
stderr: return await self.ubus_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request
|
||||
stderr: result = await self.mina_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
|
||||
stderr: return await self.account.mi_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
|
||||
stderr: raise Exception(f"Error {url}: {resp}")
|
||||
stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}}
|
||||
stderr: [08:58:05] [0.3.37] [ERROR] h11_impl.py:411: Exception in ASGI application
|
||||
stderr: Traceback (most recent call last):
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi
|
||||
stderr: result = await app( # type: ignore[func-returns-value]
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in __call__
|
||||
stderr: return await self.app(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
|
||||
stderr: await super().__call__(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in __call__
|
||||
stderr: await self.middleware_stack(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 187, in __call__
|
||||
stderr: raise exc
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 165, in __call__
|
||||
stderr: await self.app(scope, receive, _send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 85, in __call__
|
||||
stderr: await self.app(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
|
||||
stderr: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app
|
||||
stderr: raise exc
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app
|
||||
stderr: await app(scope, receive, sender)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 715, in __call__
|
||||
stderr: await self.middleware_stack(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 735, in app
|
||||
stderr: await route.handle(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle
|
||||
stderr: await self.app(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 76, in app
|
||||
stderr: await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app
|
||||
stderr: raise exc
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app
|
||||
stderr: await app(scope, receive, sender)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/starlette/routing.py", line 73, in app
|
||||
stderr: response = await f(request)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 301, in app
|
||||
stderr: raw_response = await run_endpoint_function(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 212, in run_endpoint_function
|
||||
stderr: return await dependant.call(**values)
|
||||
stderr: File "/app/xiaomusic/httpserver.py", line 124, in getvolume
|
||||
stderr: volume = await xiaomusic.get_volume(did=did)
|
||||
stderr: File "/app/xiaomusic/xiaomusic.py", line 809, in get_volume
|
||||
stderr: return await self.devices[did].get_volume()
|
||||
stderr: File "/app/xiaomusic/xiaomusic.py", line 1400, in get_volume
|
||||
stderr: playing_info = await self.xiaomusic.mina_service.player_get_status(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 103, in player_get_status
|
||||
stderr: return await self.ubus_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request
|
||||
stderr: result = await self.mina_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
|
||||
stderr: return await self.account.mi_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
|
||||
stderr: raise Exception(f"Error {url}: {resp}")
|
||||
stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_YfG6nlPVAtwDHEgMeJXxcOiZQLrR57'}}
|
||||
stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:05] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:07] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24021 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stdout: [08:58:08] [0.3.37] [INFO] 10.0.80.191:24020 - "GET /playingmusic?did=566731712 HTTP/1.1" 200
|
||||
stderr: [08:58:09] [0.3.37] [INFO] httpserver.py:177: docmd. did:566731712 cmd:播放列表m3u电台|80后音悦台
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:582: cancel_all_tasks no task
|
||||
stdout: [08:58:09] [0.3.37] [INFO] 10.0.80.191:24020 - "POST /cmd HTTP/1.1" 200
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:560: 收到消息:播放列表m3u电台|80后音悦台 控制面板:True did:566731712
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:648: 匹配到指令. opkey:播放列表 opvalue:play_music_list oparg:m3u电台|80后音悦台
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:716: 根据【m3u电台】找到播放列表【m3u电台】
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:991: 没打乱 m3u电台 ['80后音悦台', 'BBC Radio 1', 'BBC Radio 2', 'BBC Radio 3', 'BBC Radio 4', 'CNA938', 'FM 988', ' AFO Radio', 'BBC News', 'BBC Radio 1 Dance', 'BBC Radio 1 Relax', 'BBC Radio 1Xtra', 'BBC Radio 4 Extra', 'BBC Radio 5 Live', 'BBC Radio 6 Music', 'BCC中广新闻', 'BCC中广流行', 'BCC中广音乐', 'CNA亚洲新闻', 'CNR中国之声', 'CNR乡村之声', 'CNR交通广播', 'CNR台海之声', 'CNR文艺之声', 'CNR湾区之声', 'CNR神州之声', 'CNR经典音乐', 'CNR经济之声', 'CNR老年之声', 'CNR阅读之声', 'CNR音乐之声', 'CRI世界华声', 'CRI华语环球', 'CRI南海之声', 'CRI环球资讯', 'CRI英语资讯', 'CRI轻松调频', 'Capital FM', 'CityFM台之音', 'Cool Radio', 'Gold FM', 'Hit FM劲曲', 'Kiss FM', 'LBC News', 'LBC UK', 'Love FM', 'Love Radio', 'Money FM', 'NPR News', 'News Radio UK', 'One FM', 'Power FM', 'RFA', 'RFI', 'RTI中央广播', 'Times Radio', 'VOA环球英语', 'Yes FM', '上海故事广播', '上海流行音乐', '上海音乐广播', '上海音乐电台', '中文流行', '全国广播', '北京交通广播', '北京体育广播', '北京城市广播', '北京文艺广播', '北京新闻广播', '北京经典调频', '北京音乐广播', '南京音乐广播', '台中广播电台', '四川文艺广播', '国乐悠扬', '天籁之音 Hi-Fi', '天籁古典', '天籁国风', '安全百科', '山西文艺广播', '广东音乐之声', '广州汽车音乐', '摇滚天空', '有声文摘', '民谣蓝调', '民谣音乐台', '江苏故事广播', '江苏文艺广播', '江苏音乐广播', '河北文艺广播', '河北汽车音乐', '深圳音乐广播', '湖南文艺广播', '湖南旅游广播', '湖南音乐之声', '潮流音悦台', '经典FM', '美国之音', '陕西故事广播', '陕西音乐广播']
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1422: 开始播放列表m3u电台
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1005: play. search_key: name:80后音悦台
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:663: 根据【80后音悦台】找到歌曲【80后音悦台】
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1088: cur_music 80后音悦台
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:388: get_music_url web music. name:80后音悦台, url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:360: get_music_sec_url. name:80后音悦台 url:http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:362: 电台不会有播放时长
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1437: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a']
|
||||
stderr: [08:58:09] [0.3.37] [ERROR] xiaomusic.py:1137: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}}
|
||||
stderr: Traceback (most recent call last):
|
||||
stderr: File "/app/xiaomusic/xiaomusic.py", line 1131, in force_stop_xiaoai
|
||||
stderr: ret = await self.xiaomusic.mina_service.player_pause(device_id)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 79, in player_pause
|
||||
stderr: return await self.ubus_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request
|
||||
stderr: result = await self.mina_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
|
||||
stderr: return await self.account.mi_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
|
||||
stderr: raise Exception(f"Error {url}: {resp}")
|
||||
stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_10DxZOintrGWhyETo4q9auU3VMNH6A'}}
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1440: group_force_stop_xiaoai ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None]
|
||||
stderr: [08:58:09] [0.3.37] [INFO] xiaomusic.py:1091: 播放 http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8
|
||||
stderr: [08:58:10] [0.3.37] [ERROR] xiaomusic.py:1332: Execption Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}}
|
||||
stderr: Traceback (most recent call last):
|
||||
stderr: File "/app/xiaomusic/xiaomusic.py", line 1327, in play_one_url
|
||||
stderr: ret = await self.xiaomusic.mina_service.play_by_url(device_id, url)
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 125, in play_by_url
|
||||
stderr: return await self.ubus_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 59, in ubus_request
|
||||
stderr: result = await self.mina_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/minaservice.py", line 49, in mina_request
|
||||
stderr: return await self.account.mi_request(
|
||||
stderr: File "/app/.venv/lib/python3.10/site-packages/miservice/miaccount.py", line 150, in mi_request
|
||||
stderr: raise Exception(f"Error {url}: {resp}")
|
||||
stderr: Exception: Error https://api2.mina.mi.com/remote/ubus: {'code': 101, 'message': 'ubus server or device returned invalid result', 'data': {'device_data': '{"msg":"Device is offline","code":608}', 'reqID': 'app_ios_Bbkf4HLQiN7tjl1rGD2gpXuJ8yI0nP'}}
|
||||
stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1305: group_player_play http://stream3.hndt.com/now/SFZeH2cb/playlist.m3u8 ['be1daeaa-df03-4a27-8aff-404356abfa9a'] [None]
|
||||
stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1094: 播放 80后音悦台 失败
|
||||
|
||||
---
|
||||
|
||||
### 评论 4 - hanxi
|
||||
### 评论 3 - hanxi
|
||||
|
||||
设备掉线了
|
||||
|
||||
---
|
||||
|
||||
### 评论 5 - 201692929
|
||||
|
||||
怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
### 评论 6 - hanxi
|
||||
### 评论 4 - hanxi
|
||||
|
||||
> 怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 
|
||||
|
||||
@@ -269,13 +90,13 @@ stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1094: 播放 80后音悦台 失
|
||||
|
||||
---
|
||||
|
||||
### 评论 7 - 114514thD
|
||||
### 评论 5 - 114514thD
|
||||
|
||||
加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢
|
||||
|
||||
---
|
||||
|
||||
### 评论 8 - hanxi
|
||||
### 评论 6 - hanxi
|
||||
|
||||
> 加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢
|
||||
|
||||
@@ -283,7 +104,7 @@ stderr: [08:58:10] [0.3.37] [INFO] xiaomusic.py:1094: 播放 80后音悦台 失
|
||||
|
||||
---
|
||||
|
||||
### 评论 9 - 114514thD
|
||||
### 评论 7 - 114514thD
|
||||
|
||||
> > 加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢
|
||||
>
|
||||
@@ -301,31 +122,7 @@ potplayer里播放完全正常~~
|
||||
|
||||
---
|
||||
|
||||
### 评论 10 - 114514thD
|
||||
|
||||
> > 加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢
|
||||
>
|
||||
> 发出来看看?
|
||||
|
||||
经过实验发现,本地生成的m3u用potplayer播放正常
|
||||

|
||||
转换为json(去掉"type":"radio")后用小爱播放也正常
|
||||

|
||||
|
||||
但是alist链接就不正常,alist生成的m3u格式如下
|
||||
`#EXTM3U
|
||||
#EXTINF:-1,Let Me Hear.mp3
|
||||
http://192.168.1.198:5244/d/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%20%E9%9F%B3%E4%B9%90%E4%BA%91%E7%9B%98/Let%20Me%20Hear.mp3?sign=xxxx=:0`
|
||||
没有时长信息,但是用potplayer一播放就出现时长了
|
||||

|
||||
而用小爱播放就始终没有时长(切歌、等待都试过了)
|
||||

|
||||
大佬你的示例链接(gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314)里的又是正常的,感觉可能是alist的流比较特殊。。
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 评论 11 - 114514thD
|
||||
### 评论 8 - 114514thD
|
||||
|
||||
> > 加不加"type":"radio"都会一直播放不切换到下一首歌是为什么呢
|
||||
>
|
||||
@@ -335,13 +132,13 @@ http://192.168.1.198:5244/d/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%20%E9%
|
||||
|
||||
---
|
||||
|
||||
### 评论 12 - hanxi
|
||||
### 评论 9 - hanxi
|
||||
|
||||
获取歌曲时长确实有些格式获取不到。
|
||||
|
||||
---
|
||||
|
||||
### 评论 13 - 114514thD
|
||||
### 评论 10 - 114514thD
|
||||
|
||||
> 获取歌曲时长确实有些格式获取不到。
|
||||
|
||||
@@ -370,13 +167,13 @@ Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://m7.music.126.net/20241216093525/
|
||||
|
||||
---
|
||||
|
||||
### 评论 14 - hanxi
|
||||
### 评论 11 - hanxi
|
||||
|
||||
因为代码有问题。
|
||||
|
||||
---
|
||||
|
||||
### 评论 15 - 114514thD
|
||||
### 评论 12 - 114514thD
|
||||
|
||||
> 因为代码有问题。
|
||||
|
||||
@@ -384,9 +181,21 @@ Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://m7.music.126.net/20241216093525/
|
||||
|
||||
---
|
||||
|
||||
### 评论 16 - hanxi
|
||||
### 评论 13 - hanxi
|
||||
|
||||
重构方案 #314
|
||||
|
||||
---
|
||||
|
||||
### 评论 14 - LiyuTian-web
|
||||
|
||||
下指令后会重新播放歌曲。比如正在播放晴天,下指令声音小一点,这时会重新从头开始播放晴天这首歌,而不会继续播放。
|
||||
|
||||
---
|
||||
|
||||
### 评论 15 - hanxi
|
||||
|
||||
v0.3.86 版本支持 LX 歌单,歌单导出工具地址: <https://github.com/hanxi/keep-alive>
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/78)
|
||||
|
||||
@@ -4,11 +4,7 @@ title: 微信交流群二维码
|
||||
|
||||
# 微信交流群二维码
|
||||
|
||||

|
||||
|
||||
如果你刚好在买流量卡,可以在我的微信卡店里看看有没有合适的。
|
||||

|
||||
|
||||
<img width="1031" height="1440" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/2b6569c2-dabf-4b08-862f-9b0f42c6b158" />
|
||||
|
||||
## 评论
|
||||
|
||||
@@ -30,5 +26,34 @@ title: 微信交流群二维码
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
### 评论 4 - weplayro
|
||||
|
||||
二维码过期了
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 评论 5 - hanxi
|
||||
|
||||
> 二维码过期了
|
||||
|
||||
更新了
|
||||
|
||||
---
|
||||
|
||||
### 评论 6 - JmsWang
|
||||
|
||||
来晚了,又过期了
|
||||
|
||||
---
|
||||
|
||||
### 评论 7 - hanxi
|
||||
|
||||
> 来晚了,又过期了
|
||||
|
||||
更新了
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/86)
|
||||
|
||||
@@ -13,5 +13,39 @@ title: ios系统上的捷径配置
|
||||
|
||||
## 评论
|
||||
|
||||
没有评论。
|
||||
|
||||
### 评论 1 - Yumega
|
||||
|
||||
为什么我的捷径设置无效
|
||||
http://192.168.11.1:5678/cmd 这个电脑可以打开 显示 {"detail":"Method Not Allowed"}
|
||||
|
||||
参照楼主的方法 用iOS捷径无效
|
||||
|
||||
---
|
||||
|
||||
### 评论 2 - hanxi
|
||||
|
||||
方便截图看看怎么填的吗?
|
||||
|
||||
---
|
||||
|
||||
### 评论 3 - Yumega
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 评论 4 - Yumega
|
||||
|
||||
> 方便截图看看怎么填的吗?
|
||||
|
||||
😏什么原因
|
||||
|
||||
---
|
||||
|
||||
### 评论 5 - hanxi
|
||||
|
||||
@Yumega 等有空我试试,目前没时间。
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/96)
|
||||
|
||||
@@ -29,6 +29,8 @@ title: 💬 FAQ问题集合
|
||||
> 4. mi.com官网重新登陆。
|
||||
> 5. 检查 setting.json 文件里的账号密码是否正确。
|
||||
|
||||
如果是在 openwrt 类路由器系统上安装的,请检查路由器的防火墙设置。
|
||||
|
||||
## ❓ 网页后台可以播放,语音控制无效
|
||||
|
||||
这种情况是拉取不到对话记录导致的。
|
||||
@@ -51,6 +53,10 @@ title: 💬 FAQ问题集合
|
||||
|
||||
如果是配了公网反代端口,注意区分是 http 还是 https ,如果是 https 的,配置 XIAOMUSIC_HOSTNAME 时需要加上 `https:// ` 前缀。
|
||||
|
||||
如果是在 openwrt 类路由器系统上安装的,请检查路由器的防火墙设置。
|
||||
|
||||
如果是在 windows 上安装的,请关闭防火墙。另外ip别填localhost,填192开头的那个ip.
|
||||
|
||||
## ❓ 无法播放 flac 格式歌曲
|
||||
|
||||
因设备差异和文件格式差异,已知部分设备不支持 flac 格式,比如 L05B L05C 。
|
||||
@@ -219,6 +225,11 @@ docker run --rm -it browsh/browsh --startup-url https://mi.com
|
||||
]
|
||||
```
|
||||
|
||||
## 播放下一首歌曲时会重复播放上一首歌曲的前几秒
|
||||
|
||||
时间延迟问题,可以把【下一首歌延迟播放秒数】设置成负数,表示提前几秒结束播放。
|
||||
|
||||
|
||||
## 评论
|
||||
|
||||
|
||||
@@ -1715,5 +1726,93 @@ ZZZZZZ最后发现是没刷新页面, 收藏没刷新出来. 语音命令是有
|
||||
|
||||
楼主,我问题解决了,命令调用里不知道怎么回事这个搜索播放的命令没有了,重新加上就可以了,谢谢大佬
|
||||
|
||||
---
|
||||
|
||||
### 评论 112 - 22555642
|
||||
|
||||
你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
|
||||
2025/07/18 17:15:55 | stdout | [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
-- | -- | --
|
||||
2025/07/18 17:15:55 | stdout | [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:55 | stdout | ==> /app/xiaomusic.log.txt <==
|
||||
2025/07/18 17:15:55 | stdout |
|
||||
2025/07/18 17:15:55 | stderr | tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
2025/07/18 17:15:52 | stdout | 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
|
||||
2025/07/18 17:15:48 | stdout | 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
2025/07/18 17:15:48 | stdout | ==> /app/supervisord.log <==
|
||||
2025/07/18 17:15:48 | stdout |
|
||||
2025/07/18 17:15:43 | stderr | tail: /app/xiaomusic.log.txt has been replaced; following end of new file
|
||||
2025/07/18 17:15:43 | stdout | [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:43 | stdout | [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:43 | stdout | ==> /app/xiaomusic.log.txt <==
|
||||
2025/07/18 17:15:43 | stdout |
|
||||
2025/07/18 17:15:40 | stdout | 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
|
||||
2025/07/18 17:15:37 | stdout | 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
|
||||
2025/07/18 17:15:37 | stdout | ==> /app/supervisord.log <==
|
||||
2025/07/18 17:15:37 | stdout |
|
||||
2025/07/18 17:15:33 | stdout | [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:33 | stdout | [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
|
||||
2025/07/18 17:15:33 | stdout | ==> /app/xiaomusic.log.txt <==
|
||||
|
||||
---
|
||||
|
||||
### 评论 113 - daheiniu851
|
||||
|
||||
播放歌曲时一会停止,一会又继续播放 ,怎么办
|
||||
|
||||
---
|
||||
|
||||
### 评论 114 - hanxi
|
||||
|
||||
> 播放歌曲时一会停止,一会又继续播放 ,怎么办
|
||||
|
||||
是不是歌曲文件太大了?
|
||||
|
||||
---
|
||||
|
||||
### 评论 115 - 15700085709
|
||||
|
||||
nalytics.py:111: Execption Cannot connect to host umami.hanxi.cc:443 ssl:default [Connection reset by peer]
|
||||
请问这个是什么问题
|
||||
|
||||
---
|
||||
|
||||
### 评论 116 - daheiniu851
|
||||
|
||||
弄好了 再群晖里安装不可以 换飞牛正常了
|
||||
|
||||
|
||||
|
||||
---原始邮件---
|
||||
发件人: ***@***.***>
|
||||
发送时间: 2025年8月13日(周三) 中午1:49
|
||||
收件人: ***@***.***>;
|
||||
抄送: ***@***.******@***.***>;
|
||||
主题: Re: [hanxi/xiaomusic] 💬 FAQ问题集合 (Issue #99)
|
||||
|
||||
|
||||
15700085709 left a comment (hanxi/xiaomusic#99)
|
||||
|
||||
nalytics.py:111: Execption Cannot connect to host umami.hanxi.cc:443 ssl:default [Connection reset by peer]
|
||||
请问这个是什么问题
|
||||
|
||||
—
|
||||
Reply to this email directly, view it on GitHub, or unsubscribe.
|
||||
You are receiving this because you commented.Message ID: ***@***.***>
|
||||
|
||||
---
|
||||
|
||||
### 评论 117 - 467815891a
|
||||
|
||||
Bilibili现在也需要cookies,建议作者大大改一下改一下说明。按照youtube那样上传cookies就行了
|
||||
|
||||
---
|
||||
|
||||
### 评论 118 - leonardwillian13-qwe
|
||||
|
||||
在绿联nas上部署了下,测试连接可以播放,但语言指令不能执行,说了指令后就一直卡在那,本地音乐也播放不了,请问这个问题有人碰到过吗
|
||||
|
||||
[xiaomusic.txt](https://github.com/user-attachments/files/22310884/xiaomusic.txt)
|
||||
|
||||
---
|
||||
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/99)
|
||||
|
||||
@@ -1,5 +1,56 @@
|
||||
# 版本日志
|
||||
|
||||
## v0.3.87 (2025-09-11)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增 websocket 接口获取当前播放状态
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复本地播放失败问题
|
||||
|
||||
## v0.3.86 (2025-09-08)
|
||||
|
||||
### Feat
|
||||
|
||||
- LX音源支持http_proxy
|
||||
- 支持LX歌单
|
||||
- 代理播放模式使用原始地址获取歌曲时长
|
||||
- 网络歌曲支持使用代理的方式播放
|
||||
- 新增代理播放链接功能 see: #525
|
||||
|
||||
## v0.3.85 (2025-08-08)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复延迟关机按钮失效问题
|
||||
|
||||
## v0.3.84 (2025-08-03)
|
||||
|
||||
### Feat
|
||||
|
||||
- 下一首歌延迟播放秒数支持负数,用于解决播放下一首时会播放上一首的开头几秒的问题
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复谷歌字体问题
|
||||
- 文件监控: 忽略非文件创建、删除和移动事件 (#514)
|
||||
- 修复中文定时关机无法识别的BUG (#510)
|
||||
- 修复日志文件删除失败的问题
|
||||
|
||||
## v0.3.83 (2025-06-12)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增开关控制是否开始谷歌统计 see #473
|
||||
- 支持b站合集和收藏下载 (#487)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复安全问题
|
||||
- 修复安全问题
|
||||
|
||||
## v0.3.82 (2025-05-30)
|
||||
|
||||
### Fix
|
||||
|
||||
@@ -81,6 +81,13 @@ services:
|
||||
|
||||
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
|
||||
|
||||
> [!TIP]
|
||||
> 作者的另一个适用于 NAS 上安装的开源工具: <https://github.com/hanxi/tiny-nav>
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 喜欢听书的可以配合这个工具使用 <https://github.com/hanxi/epub2mp3>
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> - 🔥【广告:可用于安装 frp 实现内网穿透】
|
||||
@@ -96,7 +103,7 @@ services:
|
||||
|
||||
> [!TIP]
|
||||
> - 免费主机
|
||||
> - <a href="https://dartnode.com"><img src="https://dartnode.com/branding/DN-Open-Source-sm.png" alt="Powered by DartNode - Free VPS for Open Source" width="320"></a>
|
||||
> - <a href="https://dartnode.com?aff=SnappyPigeon570"><img src="https://dartnode.com/branding/DN-Open-Source-sm.png" alt="Powered by DartNode - Free VPS for Open Source" width="320"></a>
|
||||
|
||||
|
||||
### 🤐 支持语音口令
|
||||
@@ -114,7 +121,7 @@ services:
|
||||
- 【加入收藏】,把当前播放的歌曲加入收藏歌单。
|
||||
- 【取消收藏】,把当前播放的歌曲从收藏歌单里移除。
|
||||
- 【播放列表收藏】,这个用于播放收藏歌单。
|
||||
- 【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。
|
||||
- ~【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。~
|
||||
- 【播放列表第几个+列表名】,具体见: <https://github.com/hanxi/xiaomusic/issues/158>
|
||||
- 【搜索播放+关键词】,会搜索关键词作为临时搜索列表播放,比如说【搜索播放林俊杰】,会播放所有林俊杰的歌。
|
||||
- 【本地搜索播放+关键词】,跟搜索播放的区别是本地找不到也不会去下载。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "xiaomusic"
|
||||
version = "0.3.82"
|
||||
version = "0.3.88"
|
||||
description = "Play Music with xiaomi AI speaker"
|
||||
authors = [
|
||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||
@@ -22,7 +22,8 @@ dependencies = [
|
||||
"python-multipart>=0.0.12",
|
||||
"requests>=2.32.3",
|
||||
"sentry-sdk[fastapi]==1.45.1",
|
||||
"python-socketio>=5.12.1",
|
||||
"python-socketio>=5.12.1",
|
||||
"pyjwt>=2.10.1",
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.82"
|
||||
__version__ = "0.3.88"
|
||||
|
||||
@@ -57,8 +57,11 @@ class Analytics:
|
||||
await self._send(event)
|
||||
|
||||
async def _send(self, event):
|
||||
asyncio.create_task(self.post_to_umami(event))
|
||||
await self.run_with_cancel(self._google_send, [event])
|
||||
if self.config.enable_analytics:
|
||||
asyncio.create_task(self.post_to_umami(event))
|
||||
await self.run_with_cancel(self._google_send, [event])
|
||||
else:
|
||||
self.log.info("analytics is disabled, skip sending event")
|
||||
|
||||
def _google_send(self, events):
|
||||
try:
|
||||
|
||||
@@ -191,6 +191,9 @@ class Config:
|
||||
enable_save_tag: bool = (
|
||||
os.getenv("XIAOMUSIC_ENABLE_SAVE_TAG", "false").lower() == "true"
|
||||
)
|
||||
enable_analytics: bool = (
|
||||
os.getenv("XIAOMUSIC_ENABLE_ANALYTICS", "true").lower() == "true"
|
||||
)
|
||||
get_ask_by_mina: bool = (
|
||||
os.getenv("XIAOMUSIC_GET_ASK_BY_MINA", "false").lower() == "true"
|
||||
)
|
||||
@@ -218,6 +221,10 @@ class Config:
|
||||
)
|
||||
# 搜索歌曲数量
|
||||
search_music_count: int = int(os.getenv("XIAOMUSIC_SEARCH_MUSIC_COUNT", "100"))
|
||||
# 网络歌曲使用proxy
|
||||
web_music_proxy: bool = (
|
||||
os.getenv("XIAOMUSIC_WEB_MUSIC_PROXY", "false").lower() == "true"
|
||||
)
|
||||
|
||||
def append_keyword(self, keys, action):
|
||||
for key in keys.split(","):
|
||||
|
||||
@@ -31,6 +31,7 @@ NEED_USE_PLAY_MUSIC_API = [
|
||||
"LX05",
|
||||
"OH2",
|
||||
"OH2P",
|
||||
"X6A",
|
||||
]
|
||||
|
||||
# 有 tts command 的设备型号
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.parse
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import asdict
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import jwt
|
||||
import socketio
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from xiaomusic.xiaomusic import XiaoMusic
|
||||
@@ -19,6 +24,7 @@ if TYPE_CHECKING:
|
||||
from xiaomusic.xiaomusic import XiaoMusic
|
||||
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
from fastapi import (
|
||||
Depends,
|
||||
FastAPI,
|
||||
@@ -32,7 +38,7 @@ from fastapi import (
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.responses import RedirectResponse, StreamingResponse
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
@@ -54,6 +60,7 @@ from xiaomusic.utils import (
|
||||
remove_common_prefix,
|
||||
remove_id3_tags,
|
||||
restart_xiaomusic,
|
||||
safe_join_path,
|
||||
try_add_access_control_param,
|
||||
update_version,
|
||||
)
|
||||
@@ -338,7 +345,7 @@ async def musiclist(Verifcation=Depends(verification)):
|
||||
async def musicinfo(
|
||||
name: str, musictag: bool = False, Verifcation=Depends(verification)
|
||||
):
|
||||
url = xiaomusic.get_music_url(name)
|
||||
url, _ = await xiaomusic.get_music_url(name)
|
||||
info = {
|
||||
"ret": "OK",
|
||||
"name": name,
|
||||
@@ -357,7 +364,7 @@ async def musicinfos(
|
||||
):
|
||||
ret = []
|
||||
for music_name in name:
|
||||
url = xiaomusic.get_music_url(music_name)
|
||||
url, _ = await xiaomusic.get_music_url(music_name)
|
||||
info = {
|
||||
"name": music_name,
|
||||
"url": url,
|
||||
@@ -560,10 +567,10 @@ async def downloadplaylist(data: DownloadPlayList, Verifcation=Depends(verificat
|
||||
for title, download_proc_sigle in download_proc_list.items():
|
||||
exit_code = await download_proc_sigle.wait()
|
||||
log.info(f"Download completed {title} with exit code {exit_code}")
|
||||
dir_path = os.path.join(config.download_path, data.dirname)
|
||||
dir_path = safe_join_path(config.download_path, data.dirname)
|
||||
log.debug(f"Download dir_path: {dir_path}")
|
||||
# 可能只是部分失败,都需要整理下载目录
|
||||
remove_common_prefix(config.download_path, dir_path)
|
||||
remove_common_prefix(dir_path)
|
||||
chmoddir(dir_path)
|
||||
return {"ret": "OK"}
|
||||
else:
|
||||
@@ -574,10 +581,10 @@ async def downloadplaylist(data: DownloadPlayList, Verifcation=Depends(verificat
|
||||
exit_code = await download_proc.wait()
|
||||
log.info(f"Download completed with exit code {exit_code}")
|
||||
|
||||
dir_path = os.path.join(config.download_path, data.dirname)
|
||||
dir_path = safe_join_path(config.download_path, data.dirname)
|
||||
log.debug(f"Download dir_path: {dir_path}")
|
||||
# 可能只是部分失败,都需要整理下载目录
|
||||
remove_common_prefix(config.download_path, dir_path)
|
||||
remove_common_prefix(dir_path)
|
||||
chmoddir(dir_path)
|
||||
|
||||
asyncio.create_task(check_download_proc())
|
||||
@@ -858,3 +865,165 @@ async def get_redoc_documentation(Verifcation=Depends(verification)):
|
||||
@app.get("/openapi.json", include_in_schema=False)
|
||||
async def openapi(Verifcation=Depends(verification)):
|
||||
return get_openapi(title=app.title, version=app.version, routes=app.routes)
|
||||
|
||||
|
||||
@app.get("/proxy", summary="基于正常下载逻辑的代理接口")
|
||||
async def proxy(urlb64: str):
|
||||
try:
|
||||
# 将Base64编码的URL解码为字符串
|
||||
url_bytes = base64.b64decode(urlb64)
|
||||
url = url_bytes.decode("utf-8")
|
||||
print(f"解码后的代理请求: {url}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Base64解码失败: {str(e)}") from e
|
||||
|
||||
log.info(f"代理请求: {url}")
|
||||
|
||||
parsed_url = urlparse(url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
# Fixed: Use a new exception instance since 'e' from previous block is out of scope
|
||||
invalid_url_exc = ValueError("URL缺少协议或域名")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="无效的URL格式"
|
||||
) from invalid_url_exc
|
||||
|
||||
# 创建会话并确保关闭
|
||||
session = aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=600),
|
||||
connector=aiohttp.TCPConnector(ssl=True),
|
||||
)
|
||||
|
||||
# 复用经过验证的请求头配置
|
||||
def get_wget_headers(parsed_url):
|
||||
return {
|
||||
"User-Agent": "Wget/1.21.3",
|
||||
"Accept": "*/*",
|
||||
"Accept-Encoding": "identity",
|
||||
"Connection": "Keep-Alive",
|
||||
}
|
||||
|
||||
async def close_session():
|
||||
if not session.closed:
|
||||
await session.close()
|
||||
|
||||
try:
|
||||
# 复用download_file中的请求逻辑
|
||||
headers = get_wget_headers(parsed_url)
|
||||
resp = await session.get(url, headers=headers, allow_redirects=True)
|
||||
|
||||
if resp.status not in (200, 206):
|
||||
await close_session()
|
||||
status_exc = ValueError(f"服务器返回状态码: {resp.status}")
|
||||
raise HTTPException(
|
||||
status_code=resp.status, detail=f"下载失败,状态码: {resp.status}"
|
||||
) from status_exc
|
||||
|
||||
# 流式生成器,与download_file的分块逻辑一致
|
||||
async def stream_generator():
|
||||
try:
|
||||
async for data in resp.content.iter_chunked(4096):
|
||||
yield data
|
||||
finally:
|
||||
await close_session()
|
||||
|
||||
# 提取文件名
|
||||
filename = parsed_url.path.split("/")[-1].split("?")[0] or "output.mp3"
|
||||
|
||||
return StreamingResponse(
|
||||
stream_generator(),
|
||||
media_type=resp.headers.get("Content-Type", "audio/mpeg"),
|
||||
headers={"Content-Disposition": f'inline; filename="{filename}"'},
|
||||
background=BackgroundTask(close_session),
|
||||
)
|
||||
|
||||
except aiohttp.ClientConnectionError as e:
|
||||
await close_session()
|
||||
raise HTTPException(status_code=502, detail=f"连接错误: {str(e)}") from e
|
||||
except asyncio.TimeoutError as e:
|
||||
await close_session()
|
||||
raise HTTPException(status_code=504, detail="下载超时") from e
|
||||
except Exception as e:
|
||||
await close_session()
|
||||
raise HTTPException(status_code=500, detail=f"发生错误: {str(e)}") from e
|
||||
|
||||
|
||||
# 配置
|
||||
JWT_SECRET = secrets.token_urlsafe(32)
|
||||
JWT_ALGORITHM = "HS256"
|
||||
JWT_EXPIRE_SECONDS = 60 * 5 # 5 分钟有效期(足够前端连接和重连)
|
||||
|
||||
|
||||
@app.get("/generate_ws_token")
|
||||
def generate_ws_token(
|
||||
did: str,
|
||||
_: bool = Depends(verification), # 复用 HTTP Basic 验证
|
||||
):
|
||||
if not xiaomusic.did_exist(did):
|
||||
raise HTTPException(status_code=400, detail="Invalid did")
|
||||
|
||||
payload = {
|
||||
"did": did,
|
||||
"exp": time.time() + JWT_EXPIRE_SECONDS,
|
||||
"iat": time.time(),
|
||||
}
|
||||
|
||||
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
|
||||
|
||||
return {
|
||||
"token": token,
|
||||
"expire_in": JWT_EXPIRE_SECONDS,
|
||||
}
|
||||
|
||||
|
||||
@app.websocket("/ws/playingmusic")
|
||||
async def ws_playingmusic(websocket: WebSocket):
|
||||
token = websocket.query_params.get("token")
|
||||
if not token:
|
||||
await websocket.close(code=1008, reason="Missing token")
|
||||
return
|
||||
|
||||
try:
|
||||
# 解码 JWT(自动校验签名 + 是否过期)
|
||||
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
|
||||
did = payload.get("did")
|
||||
|
||||
if not did:
|
||||
await websocket.close(code=1008, reason="Invalid token")
|
||||
return
|
||||
|
||||
if not xiaomusic.did_exist(did):
|
||||
await websocket.close(code=1003, reason="Did not exist")
|
||||
return
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
# 开始推送状态
|
||||
while True:
|
||||
is_playing = xiaomusic.isplaying(did)
|
||||
cur_music = xiaomusic.playingmusic(did)
|
||||
cur_playlist = xiaomusic.get_cur_play_list(did)
|
||||
offset, duration = xiaomusic.get_offset_duration(did)
|
||||
|
||||
await websocket.send_text(
|
||||
json.dumps(
|
||||
{
|
||||
"ret": "OK",
|
||||
"is_playing": is_playing,
|
||||
"cur_music": cur_music,
|
||||
"cur_playlist": cur_playlist,
|
||||
"offset": offset,
|
||||
"duration": duration,
|
||||
}
|
||||
)
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except jwt.ExpiredSignatureError:
|
||||
await websocket.close(code=1008, reason="Token expired")
|
||||
except jwt.InvalidTokenError:
|
||||
await websocket.close(code=1008, reason="Invalid token")
|
||||
except WebSocketDisconnect:
|
||||
print(f"WebSocket disconnected: {did}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
await websocket.close()
|
||||
|
||||
4
xiaomusic/static/default/debug.html
vendored
4
xiaomusic/static/default/debug.html
vendored
@@ -6,9 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Debug For XiaoMusic</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1748562548">
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1758038094">
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script src="./jquery-3.7.1.min.js?version=1748562548"></script>
|
||||
<script src="./jquery-3.7.1.min.js?version=1758038094"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
|
||||
4
xiaomusic/static/default/downloadtool.html
vendored
4
xiaomusic/static/default/downloadtool.html
vendored
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>歌曲下载工具</title>
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1748562548">
|
||||
<script src="./jquery-3.7.1.min.js?version=1748562548"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1758038094">
|
||||
<script src="./jquery-3.7.1.min.js?version=1758038094"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
|
||||
12
xiaomusic/static/default/index.html
vendored
12
xiaomusic/static/default/index.html
vendored
@@ -5,9 +5,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet">
|
||||
<script src="./jquery-3.7.1.min.js?version=1748562548"></script>
|
||||
<link rel="stylesheet" href="./main.css?version=1748562548">
|
||||
<script src="./jquery-3.7.1.min.js?version=1758038094"></script>
|
||||
<link rel="stylesheet" href="./main.css?version=1758038094">
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
@@ -20,7 +19,7 @@
|
||||
</script>
|
||||
|
||||
<!-- umami -->
|
||||
<script async defer src="https://umami.hanxi.cc/script.js" data-website-id="7bfb0890-4115-4260-8892-b391513e7e99"></script>
|
||||
<!-- <script async defer src="https://umami.hanxi.cc/script.js" data-website-id="7bfb0890-4115-4260-8892-b391513e7e99"></script> -->
|
||||
</head>
|
||||
|
||||
<body class="index_page">
|
||||
@@ -154,6 +153,9 @@
|
||||
|
||||
<div class="component-button-group">
|
||||
<button onclick="playUrl()">播放链接</button>
|
||||
<button onclick="playProxyUrl()">代理播放链接</button>
|
||||
</div>
|
||||
<div class="component-button-group">
|
||||
<button onclick="playTts()">播放文字</button>
|
||||
<button onclick="togglePlayLink()">关闭</button>
|
||||
</div>
|
||||
@@ -219,7 +221,7 @@
|
||||
Powered by XiaoMusic
|
||||
</div>
|
||||
|
||||
<script src="./md.js?version=1748562548">
|
||||
<script src="./md.js?version=1758038094">
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
2
xiaomusic/static/default/m3u.html
vendored
2
xiaomusic/static/default/m3u.html
vendored
@@ -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="./main.css?version=1748562548">
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1758038094">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
|
||||
47
xiaomusic/static/default/main.css
vendored
47
xiaomusic/static/default/main.css
vendored
@@ -356,3 +356,50 @@ span,p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* fallback */
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./materialicons.woff2) format('woff2');
|
||||
}
|
||||
/* fallback */
|
||||
@font-face {
|
||||
font-family: 'Material Icons Outlined';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./materialiconsoutlined.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.material-icons-outlined {
|
||||
font-family: 'Material Icons Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
BIN
xiaomusic/static/default/materialicons.woff2
Normal file
BIN
xiaomusic/static/default/materialicons.woff2
Normal file
Binary file not shown.
BIN
xiaomusic/static/default/materialiconsoutlined.woff2
Normal file
BIN
xiaomusic/static/default/materialiconsoutlined.woff2
Normal file
Binary file not shown.
132
xiaomusic/static/default/md.js
vendored
132
xiaomusic/static/default/md.js
vendored
@@ -332,11 +332,10 @@ function refresh_music_list() {
|
||||
searchInput.value = oriValue;
|
||||
searchInput.dispatchEvent(inputEvent);
|
||||
searchInput.placeholder = oriPlaceHolder;
|
||||
// 每3秒获取下正在播放的音乐
|
||||
get_playing_music();
|
||||
setInterval(() => {
|
||||
get_playing_music();
|
||||
}, 3000);
|
||||
// 获取下正在播放的音乐
|
||||
if (did != "web_device") {
|
||||
connectWebSocket(did);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -382,6 +381,19 @@ function playUrl() {
|
||||
});
|
||||
}
|
||||
|
||||
function playProxyUrl() {
|
||||
const origin_url = $("#music-url").val();
|
||||
const protocol = window.location.protocol;
|
||||
const host= window.location.host;
|
||||
const baseUrl = `${protocol}//${host}`;
|
||||
const urlb64 = btoa(origin_url);
|
||||
const url = `${baseUrl}/proxy?urlb64=${urlb64}`;
|
||||
const encoded_url = encodeURIComponent(url);
|
||||
$.get(`/playurl?url=${encoded_url}&did=${did}`, function (data, status) {
|
||||
console.log(data);
|
||||
});
|
||||
}
|
||||
|
||||
function playTts() {
|
||||
var value = $("#text-tts").val();
|
||||
$.get(`/playtts?text=${value}&did=${did}`, function (data, status) {
|
||||
@@ -566,47 +578,6 @@ function handleSearch() {
|
||||
|
||||
handleSearch();
|
||||
|
||||
function get_playing_music() {
|
||||
$.get(`/playingmusic?did=${did}`, function (data, status) {
|
||||
console.log(data);
|
||||
if (data.ret == "OK") {
|
||||
if (data.is_playing) {
|
||||
$("#playering-music").text(`【播放中】 ${data.cur_music}`);
|
||||
isPlaying = true;
|
||||
} else {
|
||||
$("#playering-music").text(`【空闲中】 ${data.cur_music}`);
|
||||
isPlaying = false;
|
||||
}
|
||||
offset = data.offset;
|
||||
duration = data.duration;
|
||||
//检查歌曲是否在收藏中,如果是,设置收藏按钮为选中状态
|
||||
console.log(
|
||||
"%cmd.js:614 object",
|
||||
"color: #007acc;",
|
||||
favoritelist.includes(data.cur_music)
|
||||
);
|
||||
if (favoritelist.includes(data.cur_music)) {
|
||||
$(".favorite").addClass("favorite-active");
|
||||
} else {
|
||||
$(".favorite").removeClass("favorite-active");
|
||||
}
|
||||
localStorage.setItem("cur_music", data.cur_music);
|
||||
}
|
||||
});
|
||||
}
|
||||
setInterval(() => {
|
||||
if (duration > 0) {
|
||||
if (isPlaying) {
|
||||
offset++;
|
||||
$("#progress").val((offset / duration) * 100);
|
||||
$("#current-time").text(formatTime(offset));
|
||||
}
|
||||
$("#duration").text(formatTime(duration));
|
||||
} else {
|
||||
$("#current-time").text(formatTime(0));
|
||||
$("#duration").text(formatTime(0));
|
||||
}
|
||||
}, 1000);
|
||||
function formatTime(seconds) {
|
||||
var minutes = Math.floor(seconds / 60);
|
||||
var remainingSeconds = Math.floor(seconds % 60);
|
||||
@@ -722,3 +693,72 @@ function confirmSearch() {
|
||||
toggleSearch();
|
||||
}
|
||||
|
||||
|
||||
let ws = null;
|
||||
// 启动 WebSocket 连接
|
||||
function connectWebSocket(did) {
|
||||
fetch(`/generate_ws_token?did=${did}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const token = data.token;
|
||||
startWebSocket(did, token);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("获取 token 失败:", err);
|
||||
setTimeout(() => connectWebSocket(did), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function startWebSocket(did, token) {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = `${protocol}://${window.location.host}/ws/playingmusic?token=${token}`;
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.ret !== "OK") return;
|
||||
|
||||
isPlaying = data.is_playing;
|
||||
let cur_music = data.cur_music || "";
|
||||
|
||||
$("#playering-music").text(
|
||||
isPlaying ? `【播放中】 ${cur_music}` : `【空闲中】 ${cur_music}`
|
||||
);
|
||||
|
||||
offset = data.offset || 0;
|
||||
duration = data.duration || 0;
|
||||
|
||||
if (favoritelist.includes(cur_music)) {
|
||||
$(".favorite").addClass("favorite-active");
|
||||
} else {
|
||||
$(".favorite").removeClass("favorite-active");
|
||||
}
|
||||
|
||||
localStorage.setItem("cur_music", cur_music);
|
||||
updateProgressUI();
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("WebSocket 已断开,正在重连...");
|
||||
setTimeout(() => startWebSocket(did, token), 3000);
|
||||
};
|
||||
|
||||
ws.onerror = (err) => console.error("WebSocket 错误:", err);
|
||||
}
|
||||
|
||||
// 每秒更新播放进度
|
||||
function updateProgressUI() {
|
||||
const progressPercent = duration > 0 ? (offset / duration) * 100 : 0;
|
||||
$("#progress").val(progressPercent);
|
||||
$("#current-time").text(formatTime(offset));
|
||||
$("#duration").text(formatTime(duration));
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
if (duration > 0 && isPlaying) {
|
||||
offset++;
|
||||
if (offset > duration) offset = duration;
|
||||
updateProgressUI();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
|
||||
13
xiaomusic/static/default/merge/index.html
vendored
Normal file
13
xiaomusic/static/default/merge/index.html
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>歌单导出工具</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||
<link href="tailwind.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
179
xiaomusic/static/default/merge/main.js
vendored
Normal file
179
xiaomusic/static/default/merge/main.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
xiaomusic/static/default/merge/tailwind.css
vendored
Normal file
1
xiaomusic/static/default/merge/tailwind.css
vendored
Normal file
File diff suppressed because one or more lines are too long
26
xiaomusic/static/default/setting.html
vendored
26
xiaomusic/static/default/setting.html
vendored
@@ -4,10 +4,9 @@
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<script src="./jquery-3.7.1.min.js?version=1748562548"></script>
|
||||
<script src="./setting.js?version=1748562548"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1748562548">
|
||||
<script src="./jquery-3.7.1.min.js?version=1758038094"></script>
|
||||
<script src="./setting.js?version=1758038094"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./main.css?version=1758038094">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
@@ -19,7 +18,7 @@
|
||||
</script>
|
||||
|
||||
<!-- umami -->
|
||||
<script async defer src="https://umami.hanxi.cc/script.js" data-website-id="7bfb0890-4115-4260-8892-b391513e7e99"></script>
|
||||
<!-- <script async defer src="https://umami.hanxi.cc/script.js" data-website-id="7bfb0890-4115-4260-8892-b391513e7e99"></script> -->
|
||||
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
@@ -204,8 +203,8 @@ var vConsole = new window.VConsole();
|
||||
<label for="pull_ask_sec">获取对话记录间隔(秒):</label>
|
||||
<input id="pull_ask_sec" type="number" value="1" />
|
||||
|
||||
<label for="delay_sec">下一首歌延迟播放秒数:</label>
|
||||
<input id="delay_sec" type="number" value="3" />
|
||||
<label for="delay_sec">下一首歌延迟播放秒数(支持负数):</label>
|
||||
<input id="delay_sec" type="number" value="0" />
|
||||
|
||||
<label for="stop_tts_msg">停止提示音:</label>
|
||||
<input id="stop_tts_msg" type="text" value="收到,再见" />
|
||||
@@ -260,6 +259,18 @@ var vConsole = new window.VConsole();
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="enable_analytics">开启谷歌数据统计(无敏感数据):</label>
|
||||
<select id="enable_analytics">
|
||||
<option value="true" selected>true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
|
||||
<label for="web_music_proxy">网络歌曲过代理:</label>
|
||||
<select id="web_music_proxy">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="music_list_url" class="setting-label">歌单地址:
|
||||
<button class="option-inline mini-button" id="get_music_list">
|
||||
<span class="material-icons">sync_alt</span>
|
||||
@@ -300,6 +311,7 @@ var vConsole = new window.VConsole();
|
||||
<a href="/docs" target="_blank"><button>接口文档</button></a>
|
||||
<a href="./m3u.html" target="_blank"><button>m3u转换</button></a>
|
||||
<a href="./downloadtool.html" target="_blank"><button>歌曲下载工具</button></a>
|
||||
<a href="./merge/index.html" target="_blank"><button>歌单合并工具</button></a>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
|
||||
@@ -18,7 +18,9 @@ import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.parse
|
||||
from collections import OrderedDict
|
||||
from collections.abc import AsyncIterator
|
||||
from dataclasses import asdict, dataclass
|
||||
from http.cookies import SimpleCookie
|
||||
@@ -1107,14 +1109,19 @@ def _longest_common_prefix(file_names):
|
||||
return prefix
|
||||
|
||||
|
||||
# 移除目录下文件名前缀相同的
|
||||
def remove_common_prefix(safe_root, directory):
|
||||
def safe_join_path(safe_root, directory):
|
||||
directory = os.path.join(safe_root, directory)
|
||||
# Normalize the directory path
|
||||
normalized_directory = os.path.normpath(directory)
|
||||
# Ensure the directory is within the safe root
|
||||
if not normalized_directory.startswith(os.path.normpath(safe_root)):
|
||||
raise ValueError(f"Access to directory '{directory}' is not allowed.")
|
||||
files = os.listdir(normalized_directory)
|
||||
return normalized_directory
|
||||
|
||||
|
||||
# 移除目录下文件名前缀相同的
|
||||
def remove_common_prefix(directory):
|
||||
files = os.listdir(directory)
|
||||
|
||||
# 获取所有文件的前缀
|
||||
common_prefix = _longest_common_prefix(files)
|
||||
@@ -1303,3 +1310,159 @@ def chmoddir(dir_path: str):
|
||||
log.info(f"Changed permissions of file: {item_path}")
|
||||
except Exception as e:
|
||||
log.info(f"chmoddir failed: {e}")
|
||||
|
||||
|
||||
async def fetch_json_get(url, headers, config):
|
||||
connector = None
|
||||
proxy = None
|
||||
if config and config.proxy:
|
||||
connector = aiohttp.TCPConnector(
|
||||
ssl=False, # 如需验证SSL证书,可改为True(需确保代理支持)
|
||||
limit=10,
|
||||
)
|
||||
proxy = config.proxy
|
||||
try:
|
||||
# 2. 传入代理配置创建ClientSession
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
# 3. 发起带代理的GET请求
|
||||
async with session.get(
|
||||
url,
|
||||
headers=headers,
|
||||
proxy=proxy, # 传入格式化后的代理参数
|
||||
timeout=10, # 超时时间(秒),避免无限等待
|
||||
) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
log.info(f"fetch_json_get: {url} success {data}")
|
||||
|
||||
# 确保返回结果为dict
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
else:
|
||||
log.warning(f"Expected dict, but got {type(data)}: {data}")
|
||||
return {}
|
||||
else:
|
||||
log.error(f"HTTP Error: {response.status} {url}")
|
||||
return {}
|
||||
except aiohttp.ClientError as e:
|
||||
log.error(f"ClientError fetching {url} (proxy: {proxy}): {e}")
|
||||
return {}
|
||||
except asyncio.TimeoutError:
|
||||
log.error(f"Timeout fetching {url} (proxy: {proxy})")
|
||||
return {}
|
||||
except Exception as e:
|
||||
log.error(f"Unexpected error fetching {url} (proxy: {proxy}): {e}")
|
||||
return {}
|
||||
finally:
|
||||
# 4. 关闭连接器(避免资源泄漏)
|
||||
if connector and not connector.closed:
|
||||
await connector.close()
|
||||
|
||||
|
||||
class LRUCache(OrderedDict):
|
||||
def __init__(self, max_size=1000):
|
||||
super().__init__()
|
||||
self.max_size = max_size
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key in self:
|
||||
# 移动到末尾(最近使用)
|
||||
self.move_to_end(key)
|
||||
super().__setitem__(key, value)
|
||||
# 如果超出大小限制,删除最早使用的项
|
||||
if len(self) > self.max_size:
|
||||
self.popitem(last=False)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# 访问时移动到末尾(最近使用)
|
||||
if key in self:
|
||||
self.move_to_end(key)
|
||||
return super().__getitem__(key)
|
||||
|
||||
|
||||
class MusicUrlCache:
|
||||
def __init__(self, default_expire_days=1, max_size=1000):
|
||||
self.cache = LRUCache(max_size)
|
||||
self.default_expire_days = default_expire_days
|
||||
self.log = logging.getLogger(__name__)
|
||||
|
||||
async def get(self, url: str, headers: dict = None, config=None) -> str:
|
||||
"""获取URL(优先从缓存获取,没有则请求API)
|
||||
|
||||
Args:
|
||||
url: 原始URL
|
||||
headers: API请求需要的headers
|
||||
Returns:
|
||||
str: 真实播放URL
|
||||
"""
|
||||
# 先查询缓存
|
||||
cached_url = self._get_from_cache(url)
|
||||
if cached_url:
|
||||
self.log.info(f"Using cached url: {cached_url}")
|
||||
return cached_url
|
||||
|
||||
# 缓存未命中,请求API
|
||||
return await self._fetch_from_api(url, headers, config)
|
||||
|
||||
def _get_from_cache(self, url: str) -> str:
|
||||
"""从缓存中获取URL"""
|
||||
try:
|
||||
cached_url, expire_time = self.cache[url]
|
||||
if time.time() > expire_time:
|
||||
# 缓存过期,删除
|
||||
del self.cache[url]
|
||||
return ""
|
||||
return cached_url
|
||||
except KeyError:
|
||||
return ""
|
||||
|
||||
async def _fetch_from_api(self, url: str, headers: dict = None, config=None) -> str:
|
||||
"""从API获取真实URL"""
|
||||
data = await fetch_json_get(url, headers or {}, config)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
self.log.error(f"Invalid API response format: {data}")
|
||||
return ""
|
||||
|
||||
real_url = data.get("url")
|
||||
if not real_url:
|
||||
self.log.error(f"No url in API response: {data}")
|
||||
return ""
|
||||
|
||||
# 获取过期时间
|
||||
expire_time = self._parse_expire_time(data)
|
||||
|
||||
# 缓存结果
|
||||
self._set_cache(url, real_url, expire_time)
|
||||
self.log.info(
|
||||
f"Cached url, expire_time: {expire_time}, cache size: {len(self.cache)}"
|
||||
)
|
||||
return real_url
|
||||
|
||||
def _parse_expire_time(self, data: dict) -> float | None:
|
||||
"""解析API返回的过期时间"""
|
||||
try:
|
||||
extra = data.get("extra", {})
|
||||
expire_info = extra.get("expire", {})
|
||||
if expire_info and expire_info.get("canExpire"):
|
||||
expire_time = expire_info.get("time")
|
||||
if expire_time:
|
||||
return float(expire_time)
|
||||
except Exception as e:
|
||||
self.log.warning(f"Failed to parse expire time: {e}")
|
||||
return None
|
||||
|
||||
def _set_cache(self, url: str, real_url: str, expire_time: float = None):
|
||||
"""设置缓存"""
|
||||
if expire_time is None:
|
||||
expire_time = time.time() + (self.default_expire_days * 24 * 3600)
|
||||
self.cache[url] = (real_url, expire_time)
|
||||
|
||||
def clear(self):
|
||||
"""清空缓存"""
|
||||
self.cache.clear()
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
"""当前缓存大小"""
|
||||
return len(self.cache)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import base64
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
@@ -15,7 +16,12 @@ from logging.handlers import RotatingFileHandler
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from miservice import MiAccount, MiIOService, MiNAService, miio_command
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.events import (
|
||||
FileCreatedEvent,
|
||||
FileDeletedEvent,
|
||||
FileMovedEvent,
|
||||
FileSystemEventHandler,
|
||||
)
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from xiaomusic import __version__
|
||||
@@ -42,6 +48,7 @@ from xiaomusic.crontab import Crontab
|
||||
from xiaomusic.plugin import PluginManager
|
||||
from xiaomusic.utils import (
|
||||
Metadata,
|
||||
MusicUrlCache,
|
||||
chinese_to_number,
|
||||
chmodfile,
|
||||
custom_sort_key,
|
||||
@@ -76,9 +83,11 @@ class XiaoMusic:
|
||||
self.miio_service = None
|
||||
self.polling_event = asyncio.Event()
|
||||
self.new_record_event = asyncio.Event()
|
||||
self.url_cache = MusicUrlCache()
|
||||
|
||||
self.all_music = {}
|
||||
self._all_radio = {} # 电台列表
|
||||
self._web_music_api = {} # 需要通过api获取播放链接的列表
|
||||
self.music_list = {} # 播放列表 key 为目录名, value 为 play_list
|
||||
self.default_music_list_names = [] # 非自定义个歌单
|
||||
self.devices = {} # key 为 did
|
||||
@@ -171,7 +180,10 @@ class XiaoMusic:
|
||||
if log_path and not os.path.exists(log_path):
|
||||
os.makedirs(log_path)
|
||||
if os.path.exists(log_file):
|
||||
os.remove(log_file)
|
||||
try:
|
||||
os.remove(log_file)
|
||||
except Exception as e:
|
||||
self.log.warning(f"无法删除旧日志文件: {log_file} {e}")
|
||||
handler = RotatingFileHandler(
|
||||
self.config.log_file,
|
||||
maxBytes=10 * 1024 * 1024,
|
||||
@@ -457,30 +469,9 @@ class XiaoMusic:
|
||||
url = self.all_music[name]
|
||||
return url.startswith(("http://", "https://"))
|
||||
|
||||
# 获取歌曲播放时长,播放地址
|
||||
async def get_music_sec_url(self, name):
|
||||
sec = 0
|
||||
url = self.get_music_url(name)
|
||||
self.log.info(f"get_music_sec_url. name:{name} url:{url}")
|
||||
if self.is_web_radio_music(name):
|
||||
self.log.info("电台不会有播放时长")
|
||||
return 0, url
|
||||
|
||||
if self.is_web_music(name):
|
||||
origin_url = url
|
||||
duration, url = await get_web_music_duration(url, self.config)
|
||||
sec = math.ceil(duration)
|
||||
self.log.info(f"网络歌曲 {name} : {origin_url} {url} 的时长 {sec} 秒")
|
||||
else:
|
||||
filename = self.get_filename(name)
|
||||
self.log.info(f"get_music_sec_url. name:{name} filename:{filename}")
|
||||
duration = await get_local_music_duration(filename, self.config)
|
||||
sec = math.ceil(duration)
|
||||
self.log.info(f"本地歌曲 {name} : {filename} {url} 的时长 {sec} 秒")
|
||||
|
||||
if sec <= 0:
|
||||
self.log.warning(f"获取歌曲时长失败 {name} {url}")
|
||||
return sec, url
|
||||
# 是否是需要通过api获取播放链接的网络歌曲
|
||||
def is_need_use_play_music_api(self, name):
|
||||
return name in self._web_music_api
|
||||
|
||||
def get_music_tags(self, name):
|
||||
tags = copy.copy(self.all_music_tags.get(name, asdict(Metadata())))
|
||||
@@ -521,29 +512,123 @@ class XiaoMusic:
|
||||
self.try_save_tag_cache()
|
||||
return "OK"
|
||||
|
||||
def get_music_url(self, name):
|
||||
if self.is_web_music(name):
|
||||
url = self.all_music[name]
|
||||
self.log.info(f"get_music_url web music. name:{name}, url:{url}")
|
||||
return url
|
||||
async def get_music_sec_url(self, name):
|
||||
"""获取歌曲播放时长和播放地址
|
||||
|
||||
Args:
|
||||
name: 歌曲名称
|
||||
Returns:
|
||||
tuple: (播放时长(秒), 播放地址)
|
||||
"""
|
||||
url, origin_url = await self.get_music_url(name)
|
||||
self.log.info(
|
||||
f"get_music_sec_url. name:{name} url:{url} origin_url:{origin_url}"
|
||||
)
|
||||
|
||||
# 电台直接返回
|
||||
if self.is_web_radio_music(name):
|
||||
self.log.info("电台不会有播放时长")
|
||||
return 0, url
|
||||
|
||||
# 获取播放时长
|
||||
if self.is_web_music(name):
|
||||
sec = await self._get_web_music_duration(name, url, origin_url)
|
||||
else:
|
||||
sec = await self._get_local_music_duration(name, url)
|
||||
|
||||
if sec <= 0:
|
||||
self.log.warning(f"获取歌曲时长失败 {name} {url}")
|
||||
return sec, url
|
||||
|
||||
async def _get_web_music_duration(self, name, url, origin_url):
|
||||
"""获取网络音乐时长"""
|
||||
if not origin_url:
|
||||
origin_url = url if url else self.all_music[name]
|
||||
|
||||
if self.config.web_music_proxy:
|
||||
# 代理模式使用原始地址获取时长
|
||||
duration, _ = await get_web_music_duration(origin_url, self.config)
|
||||
else:
|
||||
duration, url = await get_web_music_duration(origin_url, self.config)
|
||||
|
||||
sec = math.ceil(duration)
|
||||
self.log.info(f"网络歌曲 {name} : {origin_url} {url} 的时长 {sec} 秒")
|
||||
return sec
|
||||
|
||||
async def _get_local_music_duration(self, name, url):
|
||||
"""获取本地音乐时长"""
|
||||
filename = self.get_filename(name)
|
||||
self.log.info(f"get_music_sec_url. name:{name} filename:{filename}")
|
||||
duration = await get_local_music_duration(filename, self.config)
|
||||
sec = math.ceil(duration)
|
||||
self.log.info(f"本地歌曲 {name} : {filename} {url} 的时长 {sec} 秒")
|
||||
return sec
|
||||
|
||||
async def get_music_url(self, name):
|
||||
"""获取音乐播放地址
|
||||
|
||||
Args:
|
||||
name: 歌曲名称
|
||||
Returns:
|
||||
tuple: (播放地址, 原始地址) - 网络音乐时可能有原始地址
|
||||
"""
|
||||
if self.is_web_music(name):
|
||||
return await self._get_web_music_url(name)
|
||||
return self._get_local_music_url(name), None
|
||||
|
||||
async def _get_web_music_url(self, name):
|
||||
"""获取网络音乐播放地址"""
|
||||
url = self.all_music[name]
|
||||
self.log.info(f"get_music_url web music. name:{name}, url:{url}")
|
||||
|
||||
# 需要通过API获取真实播放地址
|
||||
if self.is_need_use_play_music_api(name):
|
||||
url = await self._get_url_from_api(name, url)
|
||||
if not url:
|
||||
return "", None
|
||||
|
||||
# 是否需要代理
|
||||
if self.config.web_music_proxy:
|
||||
proxy_url = self._get_proxy_url(url)
|
||||
return proxy_url, url
|
||||
|
||||
return url, None
|
||||
|
||||
async def _get_url_from_api(self, name, url):
|
||||
"""通过API获取真实播放地址"""
|
||||
headers = self._web_music_api[name].get("headers", {})
|
||||
url = await self.url_cache.get(url, headers, self.config)
|
||||
if not url:
|
||||
self.log.error(f"get_music_url use api fail. name:{name}, url:{url}")
|
||||
return url
|
||||
|
||||
def _get_proxy_url(self, origin_url):
|
||||
"""获取代理URL"""
|
||||
urlb64 = base64.b64encode(origin_url.encode("utf-8")).decode("utf-8")
|
||||
proxy_url = f"{self.hostname}:{self.public_port}/proxy?urlb64={urlb64}"
|
||||
self.log.info(f"Using proxy url: {proxy_url}")
|
||||
return proxy_url
|
||||
|
||||
def _get_local_music_url(self, name):
|
||||
"""获取本地音乐播放地址"""
|
||||
filename = self.get_filename(name)
|
||||
|
||||
# 构造音乐文件的URL
|
||||
# 处理文件路径
|
||||
if filename.startswith(self.config.music_path):
|
||||
filename = filename[len(self.config.music_path) :]
|
||||
filename = filename.replace("\\", "/")
|
||||
if filename.startswith("/"):
|
||||
filename = filename[1:]
|
||||
|
||||
self.log.info(f"get_music_url local music. name:{name}, filename:{filename}")
|
||||
|
||||
encoded_name = urllib.parse.quote(filename)
|
||||
return try_add_access_control_param(
|
||||
self.config,
|
||||
f"{self.hostname}:{self.public_port}/music/{encoded_name}",
|
||||
self.log.info(
|
||||
f"_get_local_music_url local music. name:{name}, filename:{filename}"
|
||||
)
|
||||
|
||||
# 构造URL
|
||||
encoded_name = urllib.parse.quote(filename)
|
||||
url = f"{self.hostname}:{self.public_port}/music/{encoded_name}"
|
||||
return try_add_access_control_param(self.config, url)
|
||||
|
||||
# 给前端调用
|
||||
def refresh_music_tag(self):
|
||||
if not self.ensure_single_thread_for_tag():
|
||||
@@ -751,6 +836,7 @@ class XiaoMusic:
|
||||
return
|
||||
|
||||
self._all_radio = {}
|
||||
self._web_music_api = {}
|
||||
music_list = json.loads(self.config.music_list_json)
|
||||
try:
|
||||
for item in music_list:
|
||||
@@ -771,6 +857,8 @@ class XiaoMusic:
|
||||
# 处理电台列表
|
||||
if music_type == "radio":
|
||||
self._all_radio[name] = url
|
||||
if music.get("api"):
|
||||
self._web_music_api[name] = music
|
||||
self.log.debug(one_music_list)
|
||||
# 歌曲名字相同会覆盖
|
||||
self.music_list[list_name] = one_music_list
|
||||
@@ -1167,7 +1255,12 @@ class XiaoMusic:
|
||||
|
||||
# 定时关机
|
||||
async def stop_after_minute(self, did="", arg1=0, **kwargs):
|
||||
minute = int(arg1)
|
||||
try:
|
||||
# 尝试阿拉伯数字转换中文数字
|
||||
minute = int(arg1)
|
||||
except (KeyError, ValueError):
|
||||
# 如果阿拉伯数字转换失败,尝试中文数字
|
||||
minute = chinese_to_number(str(arg1))
|
||||
return await self.devices[did].stop_after_minute(minute)
|
||||
|
||||
# 添加歌曲到收藏列表
|
||||
@@ -2199,6 +2292,10 @@ class XiaoMusicPathWatch(FileSystemEventHandler):
|
||||
self._debounce_handle = None
|
||||
|
||||
def on_any_event(self, event):
|
||||
# 只处理文件的创建、删除和移动事件
|
||||
if not isinstance(event, FileCreatedEvent | FileDeletedEvent | FileMovedEvent):
|
||||
return
|
||||
|
||||
if event.is_directory:
|
||||
return # 忽略目录事件
|
||||
|
||||
|
||||
Reference in New Issue
Block a user