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

Compare commits

...

105 Commits

Author SHA1 Message Date
涵曦
10a24c99e4 bump: version 0.3.86 → 0.3.87 2025-09-11 07:38:08 +08:00
涵曦
9e40e4baeb build: update static version 2025-09-11 07:38:08 +08:00
Issues Docs [BOT]
5525712c77 Auto-Generate docs 🤖 2025-09-10 03:09:34 +00:00
涵曦
a36709e838 feat: 新增 websocket 接口获取当前播放状态 2025-09-09 19:55:45 +08:00
涵曦
fcfff7c090 fix: 修复本地播放失败问题 2025-09-09 09:34:10 +08:00
Issues Docs [BOT]
21186d52eb Auto-Generate docs 🤖 2025-09-08 16:00:35 +00:00
Issues Docs [BOT]
53a2f3d334 Auto-Generate docs 🤖 2025-09-08 15:54:25 +00:00
涵曦
4945bf554e bump: version 0.3.85 → 0.3.86 2025-09-08 23:52:02 +08:00
涵曦
5cb7126eec build: update static version 2025-09-08 23:52:01 +08:00
涵曦
7167670a14 feat: LX音源支持http_proxy 2025-09-08 23:39:51 +08:00
涵曦
89cb0923ee feat: 支持LX歌单 2025-09-08 19:20:35 +08:00
涵曦
2aae9ad0bc feat: 代理播放模式使用原始地址获取歌曲时长 2025-09-08 10:23:53 +08:00
Issues Docs [BOT]
94ce1f95f1 Auto-Generate docs 🤖 2025-09-04 07:55:26 +00:00
Issues Docs [BOT]
444211963b Auto-Generate docs 🤖 2025-08-27 00:49:41 +00:00
Issues Docs [BOT]
e4654944cc Auto-Generate docs 🤖 2025-08-22 11:54:52 +00:00
涵曦
01d8584fe0 feat: 网络歌曲支持使用代理的方式播放 2025-08-17 22:07:11 +08:00
Issues Docs [BOT]
80a7345ae7 Auto-Generate docs 🤖 2025-08-15 13:28:32 +00:00
Issues Docs [BOT]
5cf9a2a5f1 Auto-Generate docs 🤖 2025-08-15 13:09:42 +00:00
涵曦
6a37e1ec70 feat: 新增代理播放链接功能 see: #525 2025-08-15 20:58:49 +08:00
Issues Docs [BOT]
8246ebf74d Auto-Generate docs 🤖 2025-08-14 15:10:16 +00:00
Issues Docs [BOT]
b1e2667fad Auto-Generate docs 🤖 2025-08-13 08:44:02 +00:00
Issues Docs [BOT]
06f96fe686 Auto-Generate docs 🤖 2025-08-13 04:19:46 +00:00
涵曦
00a74fa1da Update README.md 2025-08-13 12:17:53 +08:00
Issues Docs [BOT]
f2bcb1b9aa Auto-Generate docs 🤖 2025-08-11 15:56:22 +00:00
Issues Docs [BOT]
93bdcc7ed8 Auto-Generate docs 🤖 2025-08-08 00:04:04 +00:00
涵曦
80fe648610 bump: version 0.3.84 → 0.3.85 2025-08-08 08:02:10 +08:00
涵曦
d6b530582d build: update static version 2025-08-08 08:02:10 +08:00
Issues Docs [BOT]
749fa88f62 Auto-Generate docs 🤖 2025-08-07 23:50:40 +00:00
涵曦
f2daaf77e2 fix: 修复延迟关机按钮失效问题 2025-08-08 07:42:18 +08:00
Issues Docs [BOT]
7e96c7ff33 Auto-Generate docs 🤖 2025-08-03 14:47:58 +00:00
涵曦
aea9786cd7 bump: version 0.3.83 → 0.3.84 2025-08-03 22:44:03 +08:00
涵曦
e0627dfe8b build: update static version 2025-08-03 22:44:03 +08:00
涵曦
2f1df2709e build: update static version 2025-08-03 22:38:52 +08:00
涵曦
e51f873fdf build: update static version 2025-08-03 22:37:15 +08:00
涵曦
80ead7512f update version 2025-08-03 22:14:39 +08:00
涵曦
f64d9b2462 fmt code 2025-08-03 17:08:44 +08:00
Issues Docs [BOT]
b3c6ca3943 Auto-Generate docs 🤖 2025-08-03 08:59:37 +00:00
涵曦
001e0ede20 fix: 修复谷歌字体问题 2025-08-03 16:51:08 +08:00
Issues Docs [BOT]
53f302c946 Auto-Generate docs 🤖 2025-08-02 09:54:59 +00:00
Issues Docs [BOT]
57ca7a38c4 Auto-Generate docs 🤖 2025-07-31 13:11:45 +00:00
Issues Docs [BOT]
b379cbf613 Auto-Generate docs 🤖 2025-07-30 04:31:47 +00:00
Issues Docs [BOT]
5d0e974a12 Auto-Generate docs 🤖 2025-07-24 16:49:17 +00:00
Issues Docs [BOT]
0b8af47702 Auto-Generate docs 🤖 2025-07-24 10:47:25 +00:00
涵曦
85da3069ed Update README.md 2025-07-24 18:44:30 +08:00
涵曦
d5eb5d5233 Update README.md 2025-07-24 18:43:49 +08:00
Issues Docs [BOT]
cf43527020 Auto-Generate docs 🤖 2025-07-24 03:34:20 +00:00
涵曦
5b310c269f Update README.md 2025-07-24 11:32:30 +08:00
AisukaYuki
b547fa08da fix: 文件监控: 忽略非文件创建、删除和移动事件 (#514)
只处理文件创建、删除和移动事件,避免处理不必要的事件类型
2025-07-23 12:31:34 +08:00
Issues Docs [BOT]
f27e5299a8 Auto-Generate docs 🤖 2025-07-20 07:25:17 +00:00
Issues Docs [BOT]
b566958843 Auto-Generate docs 🤖 2025-07-05 02:40:12 +00:00
Issues Docs [BOT]
faa3013898 Auto-Generate docs 🤖 2025-07-04 10:53:37 +00:00
13112442872
06de73e8ed fix: 修复中文定时关机无法识别的BUG (#510)
Co-authored-by: sj13112442872 <891771212@qq.com>
2025-07-04 11:05:57 +08:00
Issues Docs [BOT]
f4377069ad Auto-Generate docs 🤖 2025-06-29 23:27:05 +00:00
Issues Docs [BOT]
35fb9510c6 Auto-Generate docs 🤖 2025-06-27 14:35:03 +00:00
涵曦
74df4f0676 Update README.md 2025-06-27 22:33:04 +08:00
Issues Docs [BOT]
2ffb30415b Auto-Generate docs 🤖 2025-06-27 03:48:01 +00:00
Issues Docs [BOT]
71dcd2e628 Auto-Generate docs 🤖 2025-06-26 02:55:07 +00:00
Issues Docs [BOT]
65ee9de60c Auto-Generate docs 🤖 2025-06-20 15:24:52 +00:00
Issues Docs [BOT]
feaab8c1f5 Auto-Generate docs 🤖 2025-06-17 12:22:53 +00:00
Issues Docs [BOT]
ca427d69ef Auto-Generate docs 🤖 2025-06-14 15:07:24 +00:00
涵曦
1ac2c20604 feat: 下一首歌延迟播放秒数支持负数,用于解决播放下一首时会播放上一首的开头几秒的问题 2025-06-14 22:58:53 +08:00
涵曦
c2cb13e61e fix: 修复日志文件删除失败的问题 2025-06-14 22:51:47 +08:00
Issues Docs [BOT]
778d8f8b9d Auto-Generate docs 🤖 2025-06-14 10:22:35 +00:00
Issues Docs [BOT]
de3365fbbb Auto-Generate docs 🤖 2025-06-11 18:25:55 +00:00
涵曦
a316053d01 bump: version 0.3.82 → 0.3.83 2025-06-12 02:24:04 +08:00
涵曦
0bfa4e6251 build: update static version 2025-06-12 02:24:03 +08:00
涵曦
6ef7d4c6c2 feat: 新增开关控制是否开始谷歌统计 see #473 2025-06-12 01:07:33 +08:00
Issues Docs [BOT]
fff35783c2 Auto-Generate docs 🤖 2025-06-11 16:56:35 +00:00
涵曦
c8fad5ba1d Update Dockerfile.runtime 2025-06-12 00:47:56 +08:00
涵曦
956a4af3c9 fix: 修复安全问题 2025-06-10 18:46:15 +08:00
涵曦
4cf0ef2fdd Potential fix for code scanning alert no. 47: Incomplete URL substring sanitization (#497)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-10 17:24:48 +08:00
涵曦
2eb6e8035b fix: 修复安全问题 2025-06-10 16:43:43 +08:00
涵曦
1679eedb94 Potential fix for code scanning alert no. 40: Uncontrolled data used in path expression (#493)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-10 16:09:32 +08:00
涵曦
5b20b3c8eb Update Dockerfile.runtime 2025-06-10 15:13:02 +08:00
涵曦
4d07b48c63 Update Dockerfile.builder 2025-06-10 14:06:34 +08:00
涵曦
fb48ed13c0 Update Dockerfile.builder 2025-06-10 13:13:37 +08:00
涵曦
fe25ad6b81 Update Dockerfile.builder 2025-06-10 13:09:49 +08:00
涵曦
e98ec4f8e4 Update Dockerfile.builder 2025-06-10 11:24:37 +08:00
涵曦
80c4ca332e Update Dockerfile.builder 2025-06-10 08:43:46 +08:00
涵曦
9ceda0d524 Potential fix for code scanning alert no. 39: Workflow does not contain permissions (#492)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-09 23:19:49 +08:00
涵曦
b34a0fdc1e Update Dockerfile.runtime 2025-06-09 23:19:01 +08:00
涵曦
9f2dbb2457 Update Dockerfile.builder 2025-06-09 23:18:13 +08:00
Issues Docs [BOT]
684df86688 Auto-Generate docs 🤖 2025-06-09 15:02:22 +00:00
Formatter [BOT]
a62061f4ae Auto-format code 🧹🌟🤖 2025-06-09 14:57:17 +00:00
涵曦
be4028b235 Update README.md 2025-06-09 22:56:44 +08:00
Moruy
8f5fe0df98 Update Dockerfile.builder (#490) 2025-06-09 14:58:53 +08:00
Issues Docs [BOT]
6fb5917047 Auto-Generate docs 🤖 2025-06-09 04:38:17 +00:00
涵曦
a3da3523e5 Update Dockerfile 2025-06-09 12:34:31 +08:00
Moruy
5f944b8c5c feature: Migrate debian to alpine linux (#489)
* Update Dockerfile

change debian to alpine linux, reduce the image size by 50%

* Update Dockerfile.builder

change debian to alpine linux, reduce the image size by 50%

* Update Dockerfile.runtime

change debian to alpine linux, reduce the image size by 50%
2025-06-09 12:32:30 +08:00
nfzsh
c72b19ffc0 feat: 支持b站合集和收藏下载 (#487)
* feat:支持收藏夹和合集下载

* feat:支持收藏夹和合集下载

* feat:支持收藏夹和合集下载
2025-06-09 00:37:26 +08:00
Issues Docs [BOT]
acab694adc Auto-Generate docs 🤖 2025-06-08 04:45:44 +00:00
Issues Docs [BOT]
3cd1627c83 Auto-Generate docs 🤖 2025-06-07 10:15:55 +00:00
涵曦
7dfac1a71f Update README.md 2025-06-07 18:14:20 +08:00
Issues Docs [BOT]
784054b3e9 Auto-Generate docs 🤖 2025-06-07 09:53:20 +00:00
涵曦
6125d65fad Update README.md 2025-06-07 17:51:36 +08:00
Issues Docs [BOT]
17e7fc566f Auto-Generate docs 🤖 2025-06-05 03:39:22 +00:00
Issues Docs [BOT]
3193141653 Auto-Generate docs 🤖 2025-06-05 03:35:30 +00:00
Issues Docs [BOT]
01b678f4fc Auto-Generate docs 🤖 2025-06-04 04:28:07 +00:00
涵曦
c7be019b56 Update README.md 2025-06-04 12:26:25 +08:00
Issues Docs [BOT]
b005a340e1 Auto-Generate docs 🤖 2025-05-29 23:51:27 +00:00
涵曦
e4bdae4834 bump: version 0.3.81 → 0.3.82 2025-05-30 07:49:12 +08:00
涵曦
e5eb57cf05 build: update static version 2025-05-30 07:49:11 +08:00
涵曦
eb28a328ef fix: 修复节假日文件没有打包到 docker 镜像里的问题 2025-05-30 07:13:54 +08:00
Issues Docs [BOT]
e06b7a6e6c Auto-Generate docs 🤖 2025-05-29 16:28:03 +00:00
Issues Docs [BOT]
bc34c677af Auto-Generate docs 🤖 2025-05-28 15:49:21 +00:00
35 changed files with 1523 additions and 435 deletions

View File

@@ -10,6 +10,9 @@ on:
- '.github/workflows/build-base-image.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
build-image:
runs-on: ubuntu-latest

View File

@@ -1,3 +1,60 @@
## 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
- 修复节假日文件没有打包到 docker 镜像里的问题
## v0.3.81 (2025-05-28)
### Feat

View File

@@ -1,11 +1,11 @@
FROM hanxi/xiaomusic:builder AS builder
ENV DEBIAN_FRONTEND=noninteractive
RUN pip install -U pdm
ENV PDM_CHECK_UPDATE=false
WORKDIR /app
COPY pyproject.toml README.md .
COPY xiaomusic/ ./xiaomusic/
COPY plugins/ ./plugins/
COPY holiday/ ./holiday/
COPY xiaomusic.py .
RUN pdm install --prod --no-editable
@@ -14,6 +14,7 @@ WORKDIR /app
COPY --from=builder /app/.venv ./.venv
COPY --from=builder /app/xiaomusic/ ./xiaomusic/
COPY --from=builder /app/plugins/ ./plugins/
COPY --from=builder /app/holiday/ ./holiday/
COPY --from=builder /app/xiaomusic.py .
COPY --from=builder /app/xiaomusic/__init__.py /base_version.py
RUN touch /app/.dockerenv

View File

@@ -1,11 +1,14 @@
FROM python:3.10
ENV DEBIAN_FRONTEND=noninteractive
FROM python:3.12-alpine3.22
RUN apk add --no-cache --virtual .build-deps build-base python3-dev libffi-dev openssl-dev zlib-dev jpeg-dev libc6-compat gcc musl-dev
RUN pip install -U pdm
ENV PDM_CHECK_UPDATE=false
WORKDIR /app
COPY pyproject.toml README.md .
COPY xiaomusic/ ./xiaomusic/
COPY plugins/ ./plugins/
COPY xiaomusic.py .
COPY pyproject.toml README.md ./
RUN pdm install --prod --no-editable
COPY xiaomusic/ ./xiaomusic/
COPY plugins/ ./plugins/
COPY holiday/ ./holiday/
COPY xiaomusic.py .

View File

@@ -1,16 +1,18 @@
FROM python:3.10-slim
ENV DEBIAN_FRONTEND=noninteractive
FROM python:3.12-alpine3.22
RUN apt-get update && apt-get install -y \
RUN apk add --no-cache bash\
wget \
xz-utils \
libtiff6 \
libopenjp2-7 \
libxcb1 \
xz \
tiff \
openjpeg \
libxcb \
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

View File

@@ -77,10 +77,17 @@ services:
> docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
> [!TIP]
> 目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,个辛苦费 :moneybag: 50 元一次,安装失败不收费
> 目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,需要赞助个辛苦费 :moneybag: 50 元一次。
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
> [!TIP]
> 作者的另一个适用于 NAS 上安装的开源工具: <https://github.com/hanxi/tiny-nav>
> [!TIP]
>
> 喜欢听书的可以配合这个工具使用 <https://github.com/hanxi/epub2mp3>
> [!TIP]
>
> - 🔥【广告:可用于安装 frp 实现内网穿透】
@@ -94,6 +101,11 @@ services:
> - 🔥【广告: 搭建您的专属大模型主页
告别繁琐配置难题一键即可畅享稳定流畅的AI体验<https://university.aliyun.com/mobile?userCode=szqvatm6>
> [!TIP]
> - 免费主机
> - <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>
### 🤐 支持语音口令
- 【播放歌曲】,播放本地的歌曲

View File

@@ -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
我把日志发给您,您可以帮我看看嘛?
------------------&nbsp;原始邮件&nbsp;------------------
发件人: "hanxi/xiaomusic" ***@***.***&gt;;
发送时间:&nbsp;2025年7月19日(星期六) 凌晨1:31
***@***.***&gt;;
***@***.******@***.***&gt;;
主题:&nbsp;Re: [hanxi/xiaomusic] 群晖docker安装 xiaomusic (Issue #101)
hanxi left a comment (hanxi/xiaomusic#101)
你好我在群辉DOCKER上部署运行后一直打不开设置页面点开日志里面写的是这个请问要怎么办呢
2025/07/18&nbsp;17:15:55 stdout [2025-07-18&nbsp;17:15:54]&nbsp;[0.3.83]&nbsp;[INFO]&nbsp;xiaomusic.py:1373:&nbsp;The&nbsp;file&nbsp;conf/setting.json&nbsp;does&nbsp;not&nbsp;exist.
2025/07/18&nbsp;17:15:55 stdout [2025-07-18&nbsp;17:15:54]&nbsp;[0.3.83]&nbsp;[INFO]&nbsp;xiaomusic.py:1373:&nbsp;The&nbsp;file&nbsp;conf/setting.json&nbsp;does&nbsp;not&nbsp;exist.
2025/07/18&nbsp;17:15:55 stdout ==&gt;&nbsp;/app/xiaomusic.log.txt&nbsp;<==
2025/07/18&nbsp;17:15:55 stdout &nbsp;
2025/07/18&nbsp;17:15:55 stderr tail:&nbsp;/app/xiaomusic.log.txt&nbsp;has&nbsp;been&nbsp;replaced;&nbsp;following&nbsp;end&nbsp;of&nbsp;new&nbsp;file
2025/07/18&nbsp;17:15:52 stdout 2025-07-18&nbsp;17:15:51,292&nbsp;INFO&nbsp;spawned:&nbsp;'xiaomusic'&nbsp;with&nbsp;pid&nbsp;21
2025/07/18&nbsp;17:15:48 stdout 2025-07-18&nbsp;17:15:47,284&nbsp;WARN&nbsp;exited:&nbsp;xiaomusic&nbsp;(exit&nbsp;status&nbsp;1;&nbsp;not&nbsp;expected)
2025/07/18&nbsp;17:15:48 stdout ==&gt;&nbsp;/app/supervisord.log&nbsp;<==
2025/07/18&nbsp;17:15:48 stdout &nbsp;
2025/07/18&nbsp;17:15:43 stderr tail:&nbsp;/app/xiaomusic.log.txt&nbsp;has&nbsp;been&nbsp;replaced;&nbsp;following&nbsp;end&nbsp;of&nbsp;new&nbsp;file
2025/07/18&nbsp;17:15:43 stdout [2025-07-18&nbsp;17:15:42]&nbsp;[0.3.83]&nbsp;[INFO]&nbsp;xiaomusic.py:1373:&nbsp;The&nbsp;file&nbsp;conf/setting.json&nbsp;does&nbsp;not&nbsp;exist.
2025/07/18&nbsp;17:15:43 stdout [2025-07-18&nbsp;17:15:42]&nbsp;[0.3.83]&nbsp;[INFO]&nbsp;xiaomusic.py:1373:&nbsp;The&nbsp;file&nbsp;conf/setting.json&nbsp;does&nbsp;not&nbsp;exist.
2025/07/18&nbsp;17:15:43 stdout ==&gt;&nbsp;/app/xiaomusic.log.txt&nbsp;<==
2025/07/18&nbsp;17:15:43 stdout &nbsp;
2025/07/18&nbsp;17:15:40 stdout 2025-07-18&nbsp;17:15:39,479&nbsp;INFO&nbsp;spawned:&nbsp;'xiaomusic'&nbsp;with&nbsp;pid&nbsp;18
2025/07/18&nbsp;17:15:37 stdout 2025-07-18&nbsp;17:15:36,473&nbsp;WARN&nbsp;exited:&nbsp;xiaomusic&nbsp;(exit&nbsp;status&nbsp;1;&nbsp;not&nbsp;expected)
2025/07/18&nbsp;17:15:37 stdout ==&gt;&nbsp;/app/supervisord.log&nbsp;<==
2025/07/18&nbsp;17:15:37 stdout &nbsp;
2025/07/18&nbsp;17:15:33 stdout [2025-07-18&nbsp;17:15:32]&nbsp;[0.3.83]&nbsp;[INFO]&nbsp;xiaomusic.py:1373:&nbsp;The&nbsp;file&nbsp;conf/setting.json&nbsp;does&nbsp;not&nbsp;exist.
2025/07/18&nbsp;17:15:33 stdout [2025-07-18&nbsp;17:15:32]&nbsp;[0.3.83]&nbsp;[INFO]&nbsp;xiaomusic.py:1373:&nbsp;The&nbsp;file&nbsp;conf/setting.json&nbsp;does&nbsp;not&nbsp;exist.
2025/07/18&nbsp;17:15:33 stdout ==&gt;&nbsp;/app/xiaomusic.log.txt&nbsp;<==
看不出来为啥
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***&gt;
---
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/101)

View File

@@ -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_taskstop的时候在终止任务,这样就可以用关机命令了
---
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/105)

View File

@@ -4,44 +4,44 @@ title: yt-dlp cookies 文件上传功能
# yt-dlp cookies 文件上传功能
此功能用于解决 yt-dlp 下载资源失败时使用,比如 ip 被 B站或者 youtube 加入黑名单后才需要使用。
上传的文件用于 yt-dlp 的 `--cookies` 参数。
```
--cookies FILE Netscape formatted file to read cookies from
and dump cookie jar in
```
## 获取 cookies.txt 文件
1. 下载插件 [Get cookies.txt LOCALLY](https://chromewebstore.google.com/detail/cclelndahbckbenkjhflpdbgdldlbecc)
2. 给予插件访问权限和无痕模式允许使用
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/89f6ce94-bb51-4805-8c16-a867ba41e5d2)
3. 打开无痕窗口
4. 打开 youtube.com
5. 登陆 youtube.com
6. 打开新标签页
7. 关闭 youtube.com 的标签页
8. 保存 cookies.txt
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/64242595-7b5c-4159-a8bc-4fc922d5de9e)
原因见 https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies
## 上传 cookies.txt
1. 打开设置页面
2. 设置启用yt-dlp-cookies 选项为 true
![Screenshot_2024-09-29-22-31-40-134_com android chrome-edit](https://gproxy.hanxi.cc/proxy/user-attachments/assets/49760905-475b-493c-9ff4-271c5e797b2f)
3. 点击保存
4. 再点击选择文件,选择前面保存好的 cookies.txt 文件,点击上传。
![Screenshot_2024-09-29-22-33-21-361_com android chrome-edit](https://gproxy.hanxi.cc/proxy/user-attachments/assets/838bfd1c-f19f-4690-86b0-8208d596fbf1)
## 后续用途
1. 语音下载歌曲都会带上前面上传的 cookies.txt 文件去搜索下载歌曲。
2. 歌曲批量下载工具下载歌曲或者歌单时也会带上 cookies.txt 文件。
此功能用于解决 yt-dlp 下载资源失败时使用,比如 **ip 被 B站或者 youtube 加入黑名单**后才需要使用。
上传的文件用于 yt-dlp 的 `--cookies` 参数。
```
--cookies FILE Netscape formatted file to read cookies from
and dump cookie jar in
```
## 获取 cookies.txt 文件
1. 下载插件 [Get cookies.txt LOCALLY](https://chromewebstore.google.com/detail/cclelndahbckbenkjhflpdbgdldlbecc)
2. 给予插件访问权限和无痕模式允许使用
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/89f6ce94-bb51-4805-8c16-a867ba41e5d2)
3. 打开无痕窗口
4. 打开 youtube.com
5. 登陆 youtube.com
6. 打开新标签页
7. 关闭 youtube.com 的标签页
8. 保存 cookies.txt
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/64242595-7b5c-4159-a8bc-4fc922d5de9e)
原因见 https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies
## 上传 cookies.txt
1. 打开设置页面
2. 设置启用yt-dlp-cookies 选项为 true
![Screenshot_2024-09-29-22-31-40-134_com android chrome-edit](https://gproxy.hanxi.cc/proxy/user-attachments/assets/49760905-475b-493c-9ff4-271c5e797b2f)
3. 点击保存
4. 再点击选择文件,选择前面保存好的 cookies.txt 文件,点击上传。
![Screenshot_2024-09-29-22-33-21-361_com android chrome-edit](https://gproxy.hanxi.cc/proxy/user-attachments/assets/838bfd1c-f19f-4690-86b0-8208d596fbf1)
## 后续用途
1. 语音下载歌曲都会带上前面上传的 cookies.txt 文件去搜索下载歌曲。
2. 歌曲批量下载工具下载歌曲或者歌单时也会带上 cookies.txt 文件。
## 评论

View File

@@ -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)

View File

@@ -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)

View File

@@ -10,5 +10,24 @@ title: 同步网易云歌单
## 评论
没有评论。
### 评论 1 - Ghost-Sam1222
什么时候可以同步apple music歌单就真好了
---
### 评论 2 - fxchby
试了一下,似乎用不了
---
### 评论 3 - qiujie8092916
> 试了一下,似乎用不了
哪里有问题
---
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/312)

View File

@@ -105,5 +105,51 @@ services:
EOF
```
---
### 评论 3 - worrywast
为啥我的播放完一首音乐之后,还会播放该歌曲开头一点点才会放下一首呢?
---
### 评论 4 - hanxi
> 为啥我的播放完一首音乐之后,还会播放该歌曲开头一点点才会放下一首呢?
正常现象。可以把延迟设置为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)

View File

@@ -4,53 +4,57 @@ title: 已支持配置自定义网络歌单,在这里分享你的歌单
# 已支持配置自定义网络歌单,在这里分享你的歌单
设置页面新增一个输入框配置json格式可以定义配置音乐源可以是电台或者其他的m3u8格式的。
再加一个输入框配置这个json文件的url点击获取按钮把url对应的json内容填充到json输入框方便直接使用别人分享的歌单。
比如这样的链接
- https://lhttp.qtfm.cn/live/4915/64k.mp3
- http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
已经测试能播放出来:
```
python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
```
预计歌单格式是这样的, type 为 radio 作为电台的设定,会一直播放当前电台,不会播放下一首。
```json
[
{
"name":"歌单1",
"musics":[
{
"name":"歌名1",
"url":"http://ngcdn001.cnr.cn/live/zgzs/index.m3u8",
"type":"radio"
},
{
"name":"歌名2",
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
}
]
},
{
"name":"歌单2",
"musics":[
{
"name":"歌名3",
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
},
{
"name":"歌名4",
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
}
]
}
]
```
设置页面新增一个输入框配置json格式可以定义配置音乐源可以是电台或者其他的m3u8格式的。
再加一个输入框配置这个json文件的url点击获取按钮把url对应的json内容填充到json输入框方便直接使用别人分享的歌单。
比如这样的链接
- https://lhttp.qtfm.cn/live/4915/64k.mp3
- http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
已经测试能播放出来:
```
python3 micli.py play http://ngcdn001.cnr.cn/live/zgzs/index.m3u8
```
预计歌单格式是这样的, type 为 radio 作为电台的设定,会一直播放当前电台,不会播放下一首。
```json
[
{
"name":"歌单1",
"musics":[
{
"name":"歌名1",
"url":"http://ngcdn001.cnr.cn/live/zgzs/index.m3u8",
"type":"radio"
},
{
"name":"歌名2",
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
}
]
},
{
"name":"歌单2",
"musics":[
{
"name":"歌名3",
"url":"https://lhttp.qtfm.cn/live/4915/64k.mp3"
},
{
"name":"花海",
"url":"http://192.168.2.5:58090/proxy?urlb64=aHR0cHM6Ly93cy5zdHJlYW0ucXFtdXNpYy5xcS5jb20vTTgwMDAwM29rV3ZvMXFadTljLm1wMz9mcm9tdGFnPTAmZ3VpZD1mZmZmZmZmZmM4MWU2ZjVhZmZmZmZmZmZlZGZmZWI3ZiZ1aW49MzMxMjkzOTI1NCZ2a2V5PTc1MURBOEQ5RkRFNTkwOEQ5MjVCNUZFMTNBODhEREUyQkI3MzZGNDQ3NDU4MTA1OTk2Q0Q3QTEyMTQ2ODFGRUZERjNBQURDMkY0OTY2NjJEMDM1OUVDRkE0RjQwMkM2M0RDOTk4NzdDOENGMkM3OUJfX3YyMWUyYTE3MzM="
}
]
}
]
```
这里分享一个让 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
怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去
![233333](https://gproxy.hanxi.cc/proxy/user-attachments/assets/013cd952-69e9-4754-870f-2d5321865179)
---
### 评论 6 - hanxi
### 评论 4 - hanxi
> 怎么获取 他正在播放什么?或者是播放进度 ?播放列表?我想给他加进去 ![233333](https://private-user-images.githubusercontent.com/100142519/372926296-013cd952-69e9-4754-870f-2d5321865179.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mjc4ODY3MzUsIm5iZiI6MTcyNzg4NjQzNSwicGF0aCI6Ii8xMDAxNDI1MTkvMzcyOTI2Mjk2LTAxM2NkOTUyLTY5ZTktNDc1NC04NzBmLTJkNTMyMTg2NTE3OS5qcGc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQxMDAyJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MTAwMlQxNjI3MTVaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0wNzI5OTdhOTAxMmIwMDkxZTBjOGNhYTZkOWVjY2MwZTRmNGE0YTYzNDFhNGY1YzNjNTI4ZWY0YzYzYzc0Nzk3JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.4-5nGDdkDv9FRp9bAnwN4dzmf4wqKHnG4bW44BhVyRQ)
@@ -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播放正常
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/754e4344-9262-4ad1-bf17-dd83f5e3b6e5)
转换为json去掉"type":"radio")后用小爱播放也正常
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/9f1a9f02-6cf1-4536-91bd-e5e3677d6513)
但是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一播放就出现时长了
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/34ac4b9f-8b7f-40d7-9ac9-aa4621b59aa3)
而用小爱播放就始终没有时长(切歌、等待都试过了)
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/a42b62e4-7a48-46b8-b83b-a4c8cb219c0b)
大佬你的示例链接gist.github.com/hanxi/dda82d964a28f8110f8fba81c3ff8314里的又是正常的感觉可能是alist的流比较特殊。。
![image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/f84ab805-54a6-40f1-937c-67832ff0b9d6)
---
### 评论 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)

View File

@@ -4,7 +4,7 @@ title: 微信交流群二维码
# 微信交流群二维码
![Image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/bcfc59d4-dace-4aa8-bfcc-07118b10c29f)
<img width="1031" height="1440" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/ad6b6634-aa44-4cb6-baa1-0b64640b62b0" />
如果你刚好在买流量卡,可以在我的微信卡店里看看有没有合适的。
![mmexportc6bd050862507d2806a2da710a82cb28_1735878113870](https://gproxy.hanxi.cc/proxy/user-attachments/assets/ca5a86e0-f753-42d3-8dcb-8583d50d64aa)
@@ -30,5 +30,34 @@ title: 微信交流群二维码
![mm_reward_qrcode_1726365700471](https://gproxy.hanxi.cc/proxy/user-attachments/assets/7863e361-7e61-48a7-bd71-8f8f609f11b4)
---
### 评论 4 - weplayro
二维码过期了
---
### 评论 5 - hanxi
> 二维码过期了
更新了
---
### 评论 6 - JmsWang
来晚了,又过期了
---
### 评论 7 - hanxi
> 来晚了,又过期了
更新了
---
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/86)

View File

@@ -13,5 +13,39 @@ title: ios系统上的捷径配置
## 评论
没有评论。
### 评论 1 - Yumega
为什么我的捷径设置无效
http://192.168.11.1:5678/cmd 这个电脑可以打开 显示 {"detail":"Method Not Allowed"}
参照楼主的方法 用iOS捷径无效
---
### 评论 2 - hanxi
方便截图看看怎么填的吗?
---
### 评论 3 - Yumega
![Image](https://gproxy.hanxi.cc/proxy/user-attachments/assets/448e0837-665a-4c46-9598-9c747bdbc422)
---
### 评论 4 - Yumega
> 方便截图看看怎么填的吗?
😏什么原因
---
### 评论 5 - hanxi
@Yumega 等有空我试试,目前没时间。
---
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/96)

View File

@@ -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 。
@@ -201,6 +207,29 @@ docker run --rm -it browsh/browsh --startup-url https://mi.com
4. 查看容器日志中的歌曲链接是否正常,点击后台页面上的播放歌曲时,容器中会有歌曲链接,一般是 http 开头的链接,复制完整链接到浏览器试试看能否打开,能打开说明网络没问题,继续下一步排查。不能打开有可能是 ip 和端口配置错误,请使用设置页面的自动填按钮自动填 ip 和端口。
5. 歌曲文件格式是否是 mp3 格式,有些型号无法播放 flac 格式的歌曲,请使用 mp3 格式的歌曲文件测试,一个不行就多找几个文件测试。
## 在安装好第一次用语音正常,第二天就不能用语音了
拉取对话记录的问题。拉取对话记录太频繁了,可以使用[定时任务功能](/issues/182.html)设置晚上关闭拉取对话记录。比如这样设置是早上6点开启拉取对话记录晚上12点关闭
```
[
{
"expression": "0 6 * * *",
"name": "set_pull_ask",
"arg1": "enable"
},
{
"expression": "0 0 * * *",
"name": "set_pull_ask",
"arg1": "disable"
}
]
```
## 播放下一首歌曲时会重复播放上一首歌曲的前几秒
时间延迟问题,可以把【下一首歌延迟播放秒数】设置成负数,表示提前几秒结束播放。
## 评论
@@ -1697,5 +1726,85 @@ 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
弄好了 再群晖里安装不可以&nbsp; 换飞牛正常了&nbsp;&nbsp;
---原始邮件---
发件人: ***@***.***&gt;
发送时间: 2025年8月13日(周三) 中午1:49
收件人: ***@***.***&gt;;
抄送: ***@***.******@***.***&gt;;
主题: 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: ***@***.***&gt;
---
### 评论 117 - 467815891a
Bilibili现在也需要cookies建议作者大大改一下改一下说明。按照youtube那样上传cookies就行了
---
[链接到 GitHub Issue](https://github.com/hanxi/xiaomusic/issues/99)

View File

@@ -1,5 +1,63 @@
# 版本日志
## 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
- 修复节假日文件没有打包到 docker 镜像里的问题
## v0.3.81 (2025-05-28)
### Feat
- 定时任务支持工作日和休息日 see #182
### Fix
- 动态小程序码生成接口 (#478)
- 指定日志编码,避免中文乱码 (#475)
## v0.3.80 (2025-05-18)
### Feat

View File

@@ -77,10 +77,17 @@ services:
> docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
> [!TIP]
> 目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,个辛苦费 :moneybag: 50 元一次,安装失败不收费
> 目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,需要赞助个辛苦费 :moneybag: 50 元一次。
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
> [!TIP]
> 作者的另一个适用于 NAS 上安装的开源工具: <https://github.com/hanxi/tiny-nav>
> [!TIP]
>
> 喜欢听书的可以配合这个工具使用 <https://github.com/hanxi/epub2mp3>
> [!TIP]
>
> - 🔥【广告:可用于安装 frp 实现内网穿透】
@@ -94,6 +101,11 @@ services:
> - 🔥【广告: 搭建您的专属大模型主页
告别繁琐配置难题一键即可畅享稳定流畅的AI体验<https://university.aliyun.com/mobile?userCode=szqvatm6>
> [!TIP]
> - 免费主机
> - <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>
### 🤐 支持语音口令
- 【播放歌曲】,播放本地的歌曲

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.3.81"
version = "0.3.87"
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"

View File

@@ -1 +1 @@
__version__ = "0.3.81"
__version__ = "0.3.87"

View File

@@ -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:

View File

@@ -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(","):

View File

@@ -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
@@ -42,6 +48,7 @@ from starlette.responses import FileResponse, Response
from xiaomusic import __version__
from xiaomusic.utils import (
check_bili_fav_list,
chmoddir,
convert_file_to_mp3,
deepcopy_data_no_sensitive_info,
@@ -53,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,
)
@@ -337,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,
@@ -356,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,
@@ -548,14 +556,32 @@ class DownloadPlayList(BaseModel):
@app.post("/downloadplaylist")
async def downloadplaylist(data: DownloadPlayList, Verifcation=Depends(verification)):
try:
download_proc = await download_playlist(config, data.url, data.dirname)
bili_fav_list = await check_bili_fav_list(data.url)
download_proc_list = []
if bili_fav_list:
for bvid, title in bili_fav_list.items():
bvurl = f"https://www.bilibili.com/video/{bvid}"
download_proc_list[title] = await download_one_music(
config, bvurl, os.path.join(data.dirname, title)
)
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 = safe_join_path(config.download_path, data.dirname)
log.debug(f"Download dir_path: {dir_path}")
# 可能只是部分失败,都需要整理下载目录
remove_common_prefix(dir_path)
chmoddir(dir_path)
return {"ret": "OK"}
else:
download_proc = await download_playlist(config, data.url, data.dirname)
async def check_download_proc():
# 等待子进程完成
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(dir_path)
@@ -839,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()

View File

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

View File

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

View File

@@ -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=1748447212"></script>
<link rel="stylesheet" href="./main.css?version=1748447212">
<script src="./jquery-3.7.1.min.js?version=1757547485"></script>
<link rel="stylesheet" href="./main.css?version=1757547485">
<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=1748447212">
<script src="./md.js?version=1757547485">
</script>
</body>

View File

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

View File

@@ -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;
}

Binary file not shown.

Binary file not shown.

View File

@@ -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);

View File

@@ -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=1748447212"></script>
<script src="./setting.js?version=1748447212"></script>
<link rel="stylesheet" type="text/css" href="./main.css?version=1748447212">
<script src="./jquery-3.7.1.min.js?version=1757547485"></script>
<script src="./setting.js?version=1757547485"></script>
<link rel="stylesheet" type="text/css" href="./main.css?version=1757547485">
<!-- 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>

View File

@@ -18,11 +18,14 @@ 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
from urllib.parse import urlparse
from time import sleep
from urllib.parse import parse_qs, urlparse
import aiohttp
import mutagen
@@ -941,6 +944,80 @@ def _set_wave_tags(audio, info):
audio["Artist"] = info.artist
async def check_bili_fav_list(url):
bvid_info = {}
parsed_url = urlparse(url)
path = parsed_url.path
# 提取查询参数
query_params = parse_qs(parsed_url.query)
if parsed_url.hostname == "space.bilibili.com":
if "/favlist" in path:
lid = query_params.get("fid", [None])[0]
type = query_params.get("ctype", [None])[0]
if type == "11":
type = "create"
elif type == "21":
type = "collect"
else:
raise ValueError("当前只支持合集和收藏夹")
elif "/lists/" in path:
parts = path.split("/")
if len(parts) >= 4 and "?" in url:
lid = parts[3] # 提取 lid
type = query_params.get("type", [None])[0]
# https://api.bilibili.com/x/polymer/web-space/seasons_archives_list?season_id={lid}&page_size=30&page_num=1
page_size = 100
page_num = 1
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Referer": url,
"Origin": "https://space.bilibili.com",
}
async with aiohttp.ClientSession(headers=headers) as session:
if type == "season" or type == "collect":
while True:
list_url = f"https://api.bilibili.com/x/polymer/web-space/seasons_archives_list?season_id={lid}&page_size={page_size}&page_num={page_num}"
async with session.get(list_url) as response:
if response.status != 200:
raise Exception(f"Failed to fetch data from {list_url}")
data = await response.json()
archives = data.get("data", {}).get("archives", [])
if not archives:
break
for archive in archives:
bvid = archive.get("bvid", None)
title = archive.get("title", None)
bvid_info[bvid] = title
if len(archives) < page_size:
break
page_num += 1
sleep(1)
elif type == "create":
while True:
list_url = f"https://api.bilibili.com/x/v3/fav/resource/list?media_id={lid}&pn={page_num}&ps={page_size}&order=mtime"
async with session.get(list_url) as response:
if response.status != 200:
raise Exception(f"Failed to fetch data from {list_url}")
data = await response.json()
medias = data.get("data", {}).get("medias", [])
if not medias:
break
for media in medias:
bvid = media.get("bvid", None)
title = media.get("title", None)
bvurl = f"https://www.bilibili.com/video/{bvid}"
bvid_info[bvurl] = title
if len(medias) < page_size:
break
page_num += 1
else:
raise ValueError("当前只支持合集和收藏夹")
return bvid_info
# 下载播放列表
async def download_playlist(config, url, dirname):
title = f"{dirname}/%(title)s.%(ext)s"
@@ -1032,6 +1109,16 @@ def _longest_common_prefix(file_names):
return prefix
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.")
return normalized_directory
# 移除目录下文件名前缀相同的
def remove_common_prefix(directory):
files = os.listdir(directory)
@@ -1223,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)

View File

@@ -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 # 忽略目录事件