mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-07 15:02:55 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c88c79ac6 | ||
|
|
5df91e7a59 | ||
|
|
71e9c15b5d | ||
|
|
c151144a5a | ||
|
|
82a3373e72 | ||
|
|
29ef5f238f | ||
|
|
1809a2ab54 | ||
|
|
3b1684f553 | ||
|
|
d088374333 | ||
|
|
80da6bd1e6 | ||
|
|
619bb9c853 | ||
|
|
5add7b7a5c | ||
|
|
f61f14e16c | ||
|
|
125421db22 | ||
|
|
98b73f72df | ||
|
|
e68bc3b937 | ||
|
|
ab447a4633 | ||
|
|
23d321a722 | ||
|
|
20945954b1 | ||
|
|
d6c2078917 | ||
|
|
a5b8dc639c | ||
|
|
84751e0d68 | ||
|
|
e759658481 | ||
|
|
d83100588f | ||
|
|
83d0e02eb4 | ||
|
|
20f1f33b6c | ||
|
|
fbb5d26c28 | ||
|
|
2c21778675 | ||
|
|
959acd8fb7 | ||
|
|
148c5b7621 | ||
|
|
d7a2afba48 | ||
|
|
559ed23214 | ||
|
|
1d1e63df8a | ||
|
|
27e9d92a0a | ||
|
|
69573f3fa4 | ||
|
|
edafd79140 | ||
|
|
7c45d93fea | ||
|
|
f19a7e1080 | ||
|
|
5e0ae07978 | ||
|
|
116a05ce4b | ||
|
|
d6fdee5905 | ||
|
|
af25300917 | ||
|
|
f18b2f49bf | ||
|
|
db1e4e6fc4 | ||
|
|
dc49f63a37 | ||
|
|
6837841872 | ||
|
|
637347ae0c | ||
|
|
ff968c4db4 | ||
|
|
ca547e0d81 | ||
|
|
f0931c447b | ||
|
|
d098b5eb60 | ||
|
|
09111e849d | ||
|
|
49a76dee60 | ||
|
|
637672473e | ||
|
|
e4e1d13b69 | ||
|
|
7736f8c5b4 | ||
|
|
29db69b52f | ||
|
|
37fe51771b | ||
|
|
77ece713dc | ||
|
|
4436cc3a15 | ||
|
|
c875350112 | ||
|
|
d6df2f6bfe | ||
|
|
03e3312218 |
@@ -1,4 +1,5 @@
|
|||||||
FROM python:3.10 AS builder
|
FROM python:3.10 AS builder
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN python3 -m venv .venv && .venv/bin/pip install --no-cache-dir -r requirements.txt
|
RUN python3 -m venv .venv && .venv/bin/pip install --no-cache-dir -r requirements.txt
|
||||||
|
|||||||
115
README.md
115
README.md
@@ -11,6 +11,10 @@
|
|||||||
|
|
||||||
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
|
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
|
||||||
|
|
||||||
|
<https://github.com/hanxi/xiaomusic>
|
||||||
|
|
||||||
|
> 初次安装遇到问题请查阅 <https://github.com/hanxi/xiaomusic/issues/99> 上是否已经有解决办法。
|
||||||
|
|
||||||
## 最简配置运行
|
## 最简配置运行
|
||||||
|
|
||||||
已经支持在 web 页面配置其他参数,docker compose 配置如下:
|
已经支持在 web 页面配置其他参数,docker compose 配置如下:
|
||||||
@@ -28,11 +32,28 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
MI_USER: '小米账号'
|
MI_USER: '小米账号'
|
||||||
MI_PASS: '小米密码'
|
MI_PASS: '小米密码'
|
||||||
|
XIAOMUSIC_VERBOSE: 'true'
|
||||||
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
对应的 docker 启动命令如下:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
docker run -e MI_USER='小米账号' \
|
||||||
|
-e MI_PASS='小米密码' \
|
||||||
|
-e XIAOMUSIC_VERBOSE='true' \
|
||||||
|
-e XIAOMUSIC_HOSTNAME='docker 主机 ip' \
|
||||||
|
-p 8090:8090 \
|
||||||
|
-v ./music:/app/music \
|
||||||
|
hanxi/xiaomusic
|
||||||
|
```
|
||||||
|
|
||||||
启动成功后,在 web 页面可以配置 MI_DID, MI_HARDWARE, XIAOMUSIC_SEARCH, XIAOMUSIC_PROXY 参数。
|
启动成功后,在 web 页面可以配置 MI_DID, MI_HARDWARE, XIAOMUSIC_SEARCH, XIAOMUSIC_PROXY 参数。
|
||||||
|
|
||||||
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678
|
### ✨ 修改8090端口
|
||||||
|
|
||||||
|
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
xiaomusic:
|
xiaomusic:
|
||||||
@@ -46,10 +67,44 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
MI_USER: '小米账号'
|
MI_USER: '小米账号'
|
||||||
MI_PASS: '小米密码'
|
MI_PASS: '小米密码'
|
||||||
|
XIAOMUSIC_VERBOSE: 'true'
|
||||||
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
||||||
XIAOMUSIC_PORT: 5678
|
XIAOMUSIC_PORT: 5678
|
||||||
```
|
```
|
||||||
|
|
||||||
|
其中 XIAOMUSIC_VERBOSE 设置为 'true' 时表示开启 debug 日志,遇到问题可以去 web 设置页面底部【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
|
||||||
|
|
||||||
|
## pip 方式安装运行
|
||||||
|
|
||||||
|
```shell
|
||||||
|
> pip install xiaomusic
|
||||||
|
> xiaomusic --help
|
||||||
|
__ __ _ __ __ _
|
||||||
|
\ \/ / (_) __ _ ___ | \/ | _ _ ___ (_) ___
|
||||||
|
\ / | | / _` | / _ \ | |\/| | | | | | / __| | | / __|
|
||||||
|
/ \ | | | (_| | | (_) | | | | | | |_| | \__ \ | | | (__
|
||||||
|
/_/\_\ |_| \__,_| \___/ |_| |_| \__,_| |___/ |_| \___|
|
||||||
|
XiaoMusic v0.1.81 by: github.com/hanxi
|
||||||
|
|
||||||
|
usage: xiaomusic.py [-h] [--hardware HARDWARE] [--account ACCOUNT]
|
||||||
|
[--password PASSWORD] [--cookie COOKIE] [--verbose]
|
||||||
|
[--config CONFIG] [--ffmpeg_location FFMPEG_LOCATION]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--hardware HARDWARE 小爱 hardware
|
||||||
|
--account ACCOUNT xiaomi account
|
||||||
|
--password PASSWORD xiaomi password
|
||||||
|
--cookie COOKIE xiaomi cookie
|
||||||
|
--verbose show info
|
||||||
|
--config CONFIG config file path
|
||||||
|
--ffmpeg_location FFMPEG_LOCATION
|
||||||
|
ffmpeg bin path
|
||||||
|
> xiaomusic --config config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `config.json` 文件可以参考 `config-example.json` 文件配置。见 <https://github.com/hanxi/xiaomusic/issues/94>
|
||||||
|
|
||||||
## 开发环境运行
|
## 开发环境运行
|
||||||
|
|
||||||
- 使用 install_dependencies.sh 下载依赖
|
- 使用 install_dependencies.sh 下载依赖
|
||||||
@@ -86,29 +141,35 @@ pdm run xiaomusic.py
|
|||||||
|
|
||||||
## 已测试支持的设备
|
## 已测试支持的设备
|
||||||
|
|
||||||
```txt
|
| 型号 | 名称 |
|
||||||
- L07A
|
| ---- | ---------------------------------------------------------------------------------------------- |
|
||||||
- S12
|
| L06A | [小爱音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l06a) |
|
||||||
- LX5A
|
| L07A | [Redmi小爱音箱 Play](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l7a) |
|
||||||
- LX05
|
| S12 | [小米AI音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.s12) |
|
||||||
- L16A
|
| S12A | - |
|
||||||
- LX06
|
| LX5A | [小爱音箱 万能遥控版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx5a) |
|
||||||
- LX01
|
| LX05 | [小爱音箱Play(2019款)](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx05) |
|
||||||
- L05B
|
| L16A | [Xiaomi Sound](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l16a) |
|
||||||
- L05C
|
| L17A | [Xiaomi Sound Pro](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l17a) |
|
||||||
````
|
| LX06 | [小爱音箱Pro](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx06) |
|
||||||
|
| LX01 | [小爱音箱mini](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx01) |
|
||||||
|
| L05B | [小爱音箱Play](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05b) |
|
||||||
|
| L05C | [小米小爱音箱Play 增强版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05c) |
|
||||||
|
|
||||||
|
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>
|
||||||
|
|
||||||
|
> 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。
|
||||||
|
|
||||||
## 支持音乐格式
|
## 支持音乐格式
|
||||||
|
|
||||||
- mp3
|
- mp3
|
||||||
- flac
|
- flac
|
||||||
- wav
|
- wav
|
||||||
- ape
|
- ape
|
||||||
|
- ogg
|
||||||
|
|
||||||
> 本地音乐会搜索 mp3 和 flac 格式的文件,下载的歌曲是 mp3 格式的。
|
> 本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式的。
|
||||||
|
> 已知 L05B L05C 不支持 flac 格式。
|
||||||
## 其他参数
|
|
||||||
|
|
||||||
- XIAOMUSIC_ACTIVE_CMD 环境变量,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。
|
|
||||||
|
|
||||||
## 在 Docker 里使用
|
## 在 Docker 里使用
|
||||||
|
|
||||||
@@ -199,7 +260,7 @@ services:
|
|||||||
XIAOMUSIC_HOSTNAME: '192.168.2.5'
|
XIAOMUSIC_HOSTNAME: '192.168.2.5'
|
||||||
```
|
```
|
||||||
|
|
||||||
setting.json 文件不存到 music 可以这样写,会把 setting.json 文件放到容器的 /app/conf 目录且映射到本地的 ./conf 目录:
|
如果想让 setting.json 文件不存储到 music 目录,可以这样配,下面的示例会把 setting.json 文件放到容器的 /app/conf 目录且映射到本地的 ./conf 目录:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -226,11 +287,14 @@ services:
|
|||||||
|
|
||||||
- ip 是 XIAOMUSIC_HOSTNAME 设置的
|
- ip 是 XIAOMUSIC_HOSTNAME 设置的
|
||||||
- 8090 是默认端口
|
- 8090 是默认端口
|
||||||
- 新功能
|
- 支持功能
|
||||||
- 显示正在播放的歌曲
|
- 显示正在播放的歌曲
|
||||||
- 模糊搜索本地歌曲
|
- 模糊搜索本地歌曲
|
||||||
|
- 播放列表
|
||||||
|
- 删除歌曲
|
||||||
- 设置页面
|
- 设置页面
|
||||||
|
- 配置网络歌单
|
||||||
|
- 日志文件下载
|
||||||
|
|
||||||
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
|
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
|
||||||
- MI_USER
|
- MI_USER
|
||||||
@@ -245,23 +309,26 @@ services:
|
|||||||
|
|
||||||
## 网络歌单功能
|
## 网络歌单功能
|
||||||
|
|
||||||
可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,具体用法见 <https://github.com/hanxi/xiaomusic/issues/78>
|
可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,同时配备了 m3u 文件格式转换工具,可以很方便的把 m3u 电台文件转换成网络歌单格式的 json 文件,具体用法见 <https://github.com/hanxi/xiaomusic/issues/78>
|
||||||
|
|
||||||
|
> 欢迎有想法的朋友们制作更多的歌单转换工具。
|
||||||
|
|
||||||
## 更多其他可选配置
|
## 更多其他可选配置
|
||||||
|
|
||||||
- XIAOMUSIC_ACTIVE_CMD 配置唤醒命令,具体见 <https://github.com/hanxi/xiaomusic/pull/43>
|
- XIAOMUSIC_ACTIVE_CMD 环境变量,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 <https://github.com/hanxi/xiaomusic/pull/43>
|
||||||
- XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录
|
- XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录
|
||||||
- XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,具体见 <https://github.com/hanxi/xiaomusic/issues/76>
|
- XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,具体见 <https://github.com/hanxi/xiaomusic/issues/76>
|
||||||
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,具体见 <https://github.com/hanxi/xiaomusic/issues/47>
|
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,具体见 <https://github.com/hanxi/xiaomusic/issues/47>
|
||||||
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户
|
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户
|
||||||
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
|
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
|
||||||
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认情况会把配置存放在music目录,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
|
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认情况会把配置存放在music目录,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
|
||||||
- XIAOMUSIC_VERBOSE 设置为 true 时开启 debug 日志,用于排查问题
|
|
||||||
- XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,见 <https://github.com/hanxi/xiaomusic/issues/82>
|
- XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,见 <https://github.com/hanxi/xiaomusic/issues/82>
|
||||||
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号
|
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,v0.1.86 之后的版本可以不用设置。
|
||||||
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
||||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
||||||
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
||||||
|
- XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,支持模糊匹配歌名和歌单名。 具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||||
|
- XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准。具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||||
|
|
||||||
## 讨论区
|
## 讨论区
|
||||||
|
|
||||||
|
|||||||
32
config-example.json
Normal file
32
config-example.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"hardware": "L07A",
|
||||||
|
"account": "",
|
||||||
|
"password": "",
|
||||||
|
"mi_did": "",
|
||||||
|
"cookie": "",
|
||||||
|
"verbose": false,
|
||||||
|
"music_path": "music",
|
||||||
|
"conf_path": null,
|
||||||
|
"hostname": "192.168.2.5",
|
||||||
|
"port": 8090,
|
||||||
|
"proxy": null,
|
||||||
|
"search_prefix": "ytsearch:",
|
||||||
|
"ffmpeg_location": "./ffmpeg/bin",
|
||||||
|
"active_cmd": "play,random_play,playlocal,play_music_list,stop",
|
||||||
|
"exclude_dirs": "@eaDir",
|
||||||
|
"music_path_depth": 10,
|
||||||
|
"disable_httpauth": true,
|
||||||
|
"httpauth_username": "admin",
|
||||||
|
"httpauth_password": "admin",
|
||||||
|
"music_list_url": "",
|
||||||
|
"music_list_json": "",
|
||||||
|
"disable_download": false,
|
||||||
|
"use_music_api": false,
|
||||||
|
"log_file": "/tmp/xiaomusic.txt",
|
||||||
|
"fuzzy_match_cutoff": 0.6,
|
||||||
|
"enable_fuzzy_match": true,
|
||||||
|
"stop_tts_msg": "收到,再见",
|
||||||
|
"keywords_playlocal": "播放本地歌曲,本地播放歌曲",
|
||||||
|
"keywords_play": "播放歌曲,放歌曲",
|
||||||
|
"keywords_stop": "关机,暂停,停止,停止播放"
|
||||||
|
}
|
||||||
@@ -4,14 +4,41 @@
|
|||||||
# https://github.com/yt-dlp/yt-dlp#dependencies
|
# https://github.com/yt-dlp/yt-dlp#dependencies
|
||||||
|
|
||||||
# 判断系统架构
|
# 判断系统架构
|
||||||
arch=$(arch)
|
arch=$(uname -m)
|
||||||
|
|
||||||
pkg=ffmpeg-master-latest-linuxarm64-gpl
|
# 输出架构信息
|
||||||
if [[ "${arch}" == "x86_64" ]]; then
|
echo "当前系统架构是:$arch"
|
||||||
|
|
||||||
|
install_from_build() {
|
||||||
|
pkg=$1
|
||||||
|
wget https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/$pkg.tar.xz
|
||||||
|
tar -xvJf $pkg.tar.xz
|
||||||
|
mv $pkg ffmpeg
|
||||||
|
}
|
||||||
|
|
||||||
|
install_from_apt() {
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y ffmpeg
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
mkdir -p /app/ffmpeg/bin
|
||||||
|
ln -s /usr/bin/ffmpeg /app/ffmpeg/bin/ffmpeg
|
||||||
|
ln -s /usr/bin/ffprobe /app/ffmpeg/bin/ffprobe
|
||||||
|
}
|
||||||
|
|
||||||
|
# 基于架构执行不同的操作
|
||||||
|
case "$arch" in
|
||||||
|
x86_64)
|
||||||
|
echo "64位 x86 架构"
|
||||||
pkg=ffmpeg-master-latest-linux64-gpl
|
pkg=ffmpeg-master-latest-linux64-gpl
|
||||||
fi
|
install_from_build "$pkg"
|
||||||
|
;;
|
||||||
#export ALL_PROXY=http://192.168.2.5:8080
|
arm64 | aarch64)
|
||||||
wget https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/$pkg.tar.xz
|
echo "64位 ARM 架构"
|
||||||
tar -xvJf $pkg.tar.xz
|
pkg=ffmpeg-master-latest-linuxarm64-gpl
|
||||||
mv $pkg ffmpeg
|
install_from_build "$pkg"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "未知架构 $arch"
|
||||||
|
install_from_apt
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
24
pdm.lock
generated
24
pdm.lock
generated
@@ -5,7 +5,7 @@
|
|||||||
groups = ["default", "lint"]
|
groups = ["default", "lint"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:813253734c7d7835a76cd87fe8fe0329e02ad067f535aee6a9e11cb106569dd2"
|
content_hash = "sha256:e7455b13bf13306ccf5ad11781191edb62991d9fbe8f8ce1e61a2f35c713cc2a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohttp"
|
name = "aiohttp"
|
||||||
@@ -519,7 +519,7 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miservice-fork"
|
name = "miservice-fork"
|
||||||
version = "2.6.0"
|
version = "2.6.1"
|
||||||
requires_python = ">=3.8"
|
requires_python = ">=3.8"
|
||||||
summary = "XiaoMi Cloud Service fork from https://github.com/Yonsm/MiService"
|
summary = "XiaoMi Cloud Service fork from https://github.com/Yonsm/MiService"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -528,8 +528,8 @@ dependencies = [
|
|||||||
"rich",
|
"rich",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "miservice_fork-2.6.0-py3-none-any.whl", hash = "sha256:98169a77ea41a7b9392e1b1fab8cb80a4165fed8a9e882d9ada9a16dd1120347"},
|
{file = "miservice_fork-2.6.1-py3-none-any.whl", hash = "sha256:9b2cc4208486bbbf788d1bde6e2cbc70f241ce10db4dca6f918076a2d2942a39"},
|
||||||
{file = "miservice_fork-2.6.0.tar.gz", hash = "sha256:a59d337d1f7a92566aa147e96595a8d2f5bf3f7000ae5e7dd9ed451f18d6e2fd"},
|
{file = "miservice_fork-2.6.1.tar.gz", hash = "sha256:1702281e1e9827958eb3e82bc3242cd013c018e9aa1de8509b4805b5ccf5e60c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -632,8 +632,8 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.32.3"
|
version = "2.31.0"
|
||||||
requires_python = ">=3.8"
|
requires_python = ">=3.7"
|
||||||
summary = "Python HTTP for Humans."
|
summary = "Python HTTP for Humans."
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"certifi>=2017.4.17",
|
"certifi>=2017.4.17",
|
||||||
@@ -642,8 +642,8 @@ dependencies = [
|
|||||||
"urllib3<3,>=1.21.1",
|
"urllib3<3,>=1.21.1",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
||||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -832,7 +832,7 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yt-dlp"
|
name = "yt-dlp"
|
||||||
version = "2024.6.24.232830.dev0"
|
version = "2024.5.30.232720.dev0"
|
||||||
requires_python = ">=3.8"
|
requires_python = ">=3.8"
|
||||||
summary = "A feature-rich command-line audio/video downloader"
|
summary = "A feature-rich command-line audio/video downloader"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -841,11 +841,11 @@ dependencies = [
|
|||||||
"certifi",
|
"certifi",
|
||||||
"mutagen",
|
"mutagen",
|
||||||
"pycryptodomex",
|
"pycryptodomex",
|
||||||
"requests<3,>=2.32.2",
|
"requests<3,>=2.31.0",
|
||||||
"urllib3<3,>=1.26.17",
|
"urllib3<3,>=1.26.17",
|
||||||
"websockets>=12.0",
|
"websockets>=12.0",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "yt_dlp-2024.6.24.232830.dev0-py3-none-any.whl", hash = "sha256:efffecef44ce688e9ee3c02226eb1ba4ad64b37744726e9e4df5c2bd04ea93c5"},
|
{file = "yt_dlp-2024.5.30.232720.dev0-py3-none-any.whl", hash = "sha256:d6e563a2923807392325722028e7792e35affb694a505617b008195d0d212d2c"},
|
||||||
{file = "yt_dlp-2024.6.24.232830.dev0.tar.gz", hash = "sha256:0e89b46958984954393692a8c41e0f6d76a773be2df381c3d3a4ff24ce89aa32"},
|
{file = "yt_dlp-2024.5.30.232720.dev0.tar.gz", hash = "sha256:9e2b177c5b13ea6f54cee1c56a69dd7832d506fba73a2247c6470e7d1952f959"},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "xiaomusic"
|
name = "xiaomusic"
|
||||||
version = "0.1.73"
|
version = "0.1.87"
|
||||||
description = "Play Music with xiaomi AI speaker"
|
description = "Play Music with xiaomi AI speaker"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"requests>=2.31.0",
|
"requests==2.31.0",
|
||||||
"aiohttp>=3.8.6",
|
"aiohttp>=3.8.6",
|
||||||
"miservice-fork>=2.5.0",
|
"miservice-fork>=2.5.0",
|
||||||
"mutagen>=1.47.0",
|
"mutagen>=1.47.0",
|
||||||
@@ -19,6 +19,12 @@ requires-python = ">=3.10"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/hanxi/xiaomusic"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
xiaomusic = "xiaomusic.cli:main"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["pdm-backend"]
|
requires = ["pdm-backend"]
|
||||||
build-backend = "pdm.backend"
|
build-backend = "pdm.backend"
|
||||||
|
|||||||
@@ -305,9 +305,9 @@ MarkupSafe==2.1.4 \
|
|||||||
mdurl==0.1.2 \
|
mdurl==0.1.2 \
|
||||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||||
miservice-fork==2.6.0 \
|
miservice-fork==2.6.1 \
|
||||||
--hash=sha256:98169a77ea41a7b9392e1b1fab8cb80a4165fed8a9e882d9ada9a16dd1120347 \
|
--hash=sha256:1702281e1e9827958eb3e82bc3242cd013c018e9aa1de8509b4805b5ccf5e60c \
|
||||||
--hash=sha256:a59d337d1f7a92566aa147e96595a8d2f5bf3f7000ae5e7dd9ed451f18d6e2fd
|
--hash=sha256:9b2cc4208486bbbf788d1bde6e2cbc70f241ce10db4dca6f918076a2d2942a39
|
||||||
multidict==6.0.4 \
|
multidict==6.0.4 \
|
||||||
--hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \
|
--hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \
|
||||||
--hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \
|
--hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \
|
||||||
@@ -371,9 +371,9 @@ pycryptodomex==3.19.0 \
|
|||||||
pygments==2.16.1 \
|
pygments==2.16.1 \
|
||||||
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
|
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
|
||||||
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
|
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
|
||||||
requests==2.32.3 \
|
requests==2.31.0 \
|
||||||
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
|
||||||
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
|
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
|
||||||
rich==13.7.1 \
|
rich==13.7.1 \
|
||||||
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \
|
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \
|
||||||
--hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432
|
--hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432
|
||||||
@@ -472,6 +472,6 @@ yarl==1.9.2 \
|
|||||||
--hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \
|
--hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \
|
||||||
--hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \
|
--hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \
|
||||||
--hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560
|
--hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560
|
||||||
yt-dlp==2024.6.24.232830.dev0 \
|
yt-dlp==2024.5.30.232720.dev0 \
|
||||||
--hash=sha256:0e89b46958984954393692a8c41e0f6d76a773be2df381c3d3a4ff24ce89aa32 \
|
--hash=sha256:9e2b177c5b13ea6f54cee1c56a69dd7832d506fba73a2247c6470e7d1952f959 \
|
||||||
--hash=sha256:efffecef44ce688e9ee3c02226eb1ba4ad64b37744726e9e4df5c2bd04ea93c5
|
--hash=sha256:d6e563a2923807392325722028e7792e35affb694a505617b008195d0d212d2c
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.73"
|
__version__ = "0.1.87"
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from xiaomusic import (
|
||||||
|
__version__,
|
||||||
|
)
|
||||||
from xiaomusic.config import Config
|
from xiaomusic.config import Config
|
||||||
from xiaomusic.xiaomusic import XiaoMusic
|
from xiaomusic.xiaomusic import XiaoMusic
|
||||||
|
|
||||||
|
LOGO = r"""
|
||||||
|
__ __ _ __ __ _
|
||||||
|
\ \/ / (_) __ _ ___ | \/ | _ _ ___ (_) ___
|
||||||
|
\ / | | / _` | / _ \ | |\/| | | | | | / __| | | / __|
|
||||||
|
/ \ | | | (_| | | (_) | | | | | | |_| | \__ \ | | | (__
|
||||||
|
/_/\_\ |_| \__,_| \___/ |_| |_| \__,_| |___/ |_| \___|
|
||||||
|
{}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@@ -45,6 +57,8 @@ def main():
|
|||||||
help="ffmpeg bin path",
|
help="ffmpeg bin path",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print(LOGO.format(f"XiaoMusic v{__version__} by: github.com/hanxi"))
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
config = Config.from_options(options)
|
config = Config.from_options(options)
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ class Config:
|
|||||||
"XIAOMUSIC_SEARCH", "ytsearch:"
|
"XIAOMUSIC_SEARCH", "ytsearch:"
|
||||||
) # "bilisearch:" or "ytsearch:"
|
) # "bilisearch:" or "ytsearch:"
|
||||||
ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
|
ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
|
||||||
active_cmd: str = os.getenv("XIAOMUSIC_ACTIVE_CMD", "play,random_play,playlocal,play_music_list")
|
active_cmd: str = os.getenv(
|
||||||
|
"XIAOMUSIC_ACTIVE_CMD", "play,random_play,playlocal,play_music_list,stop"
|
||||||
|
)
|
||||||
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir")
|
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir")
|
||||||
music_path_depth: int = int(os.getenv("XIAOMUSIC_MUSIC_PATH_DEPTH", "10"))
|
music_path_depth: int = int(os.getenv("XIAOMUSIC_MUSIC_PATH_DEPTH", "10"))
|
||||||
disable_httpauth: bool = (
|
disable_httpauth: bool = (
|
||||||
@@ -79,6 +81,20 @@ class Config:
|
|||||||
use_music_api: bool = (
|
use_music_api: bool = (
|
||||||
os.getenv("XIAOMUSIC_USE_MUSIC_API", "false").lower() == "true"
|
os.getenv("XIAOMUSIC_USE_MUSIC_API", "false").lower() == "true"
|
||||||
)
|
)
|
||||||
|
log_file: str = os.getenv("XIAOMUSIC_MUSIC_LOG_FILE", "/tmp/xiaomusic.txt")
|
||||||
|
# 模糊搜索匹配的最低相似度阈值
|
||||||
|
fuzzy_match_cutoff: float = float(os.getenv("XIAOMUSIC_FUZZY_MATCH_CUTOFF", "0.6"))
|
||||||
|
# 开启模糊搜索
|
||||||
|
enable_fuzzy_match: bool = (
|
||||||
|
os.getenv("XIAOMUSIC_ENABLE_FUZZY_MATCH", "true").lower() == "true"
|
||||||
|
)
|
||||||
|
stop_tts_msg: str = os.getenv("XIAOMUSIC_STOP_TTS_MSG", "收到,再见")
|
||||||
|
|
||||||
|
keywords_playlocal: str = os.getenv(
|
||||||
|
"XIAOMUSIC_KEYWORDS_PLAYLOCAL", "播放本地歌曲,本地播放歌曲"
|
||||||
|
)
|
||||||
|
keywords_play: str = os.getenv("XIAOMUSIC_KEYWORDS_PLAY", "播放歌曲,放歌曲")
|
||||||
|
keywords_stop: str = os.getenv("XIAOMUSIC_KEYWORDS_STOP", "关机,暂停,停止,停止播放")
|
||||||
|
|
||||||
def append_keyword(self, keys, action):
|
def append_keyword(self, keys, action):
|
||||||
for key in keys.split(","):
|
for key in keys.split(","):
|
||||||
@@ -89,14 +105,14 @@ class Config:
|
|||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if self.proxy:
|
if self.proxy:
|
||||||
validate_proxy(self.proxy)
|
validate_proxy(self.proxy)
|
||||||
keywords_playlocal = os.getenv(
|
self.append_keyword(self.keywords_playlocal, "playlocal")
|
||||||
"XIAOMUSIC_KEYWORDS_PLAYLOCAL", "播放本地歌曲,本地播放歌曲"
|
self.append_keyword(self.keywords_play, "play")
|
||||||
)
|
self.append_keyword(self.keywords_stop, "stop")
|
||||||
self.append_keyword(keywords_playlocal, "playlocal")
|
|
||||||
keywords_play = os.getenv("XIAOMUSIC_KEYWORDS_PLAY", "播放歌曲,放歌曲")
|
# 保存配置到 config-example.json 文件
|
||||||
self.append_keyword(keywords_play, "play")
|
# with open("config-example.json", "w") as f:
|
||||||
keywords_stop = os.getenv("XIAOMUSIC_KEYWORDS_STOP", "关机,暂停,停止")
|
# data = asdict(self)
|
||||||
self.append_keyword(keywords_stop, "stop")
|
# json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_options(cls, options: argparse.Namespace) -> Config:
|
def from_options(cls, options: argparse.Namespace) -> Config:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ SUPPORT_MUSIC_TYPE = [
|
|||||||
".flac",
|
".flac",
|
||||||
".wav",
|
".wav",
|
||||||
".ape",
|
".ape",
|
||||||
|
".ogg",
|
||||||
]
|
]
|
||||||
|
|
||||||
LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}×tamp={timestamp}&limit=2"
|
LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}×tamp={timestamp}&limit=2"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from flask import Flask, request, send_from_directory
|
from flask import Flask, request, send_file, send_from_directory
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
@@ -163,6 +163,30 @@ def downloadjson():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/downloadlog", methods=["GET"])
|
||||||
|
@auth.login_required
|
||||||
|
def downloadlog():
|
||||||
|
return send_file(xiaomusic.config.log_file, as_attachment=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/playurl", methods=["GET"])
|
||||||
|
@auth.login_required
|
||||||
|
async def playurl():
|
||||||
|
url = request.args.get("url")
|
||||||
|
log.info(f"play_url:{url}")
|
||||||
|
return await xiaomusic.call_main_thread_function(xiaomusic.play_url, arg1=url)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/debug_play_by_music_url", methods=["POST"])
|
||||||
|
@auth.login_required
|
||||||
|
async def debug_play_by_music_url():
|
||||||
|
data = request.get_json()
|
||||||
|
log.info(f"data:{data}")
|
||||||
|
return await xiaomusic.call_main_thread_function(
|
||||||
|
xiaomusic.debug_play_by_music_url, arg1=data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def static_path_handler(filename):
|
def static_path_handler(filename):
|
||||||
log.debug(filename)
|
log.debug(filename)
|
||||||
log.debug(static_path)
|
log.debug(static_path)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ $(function(){
|
|||||||
// 拉取版本
|
// 拉取版本
|
||||||
$.get("/getversion", function(data, status) {
|
$.get("/getversion", function(data, status) {
|
||||||
console.log(data, status, data["version"]);
|
console.log(data, status, data["version"]);
|
||||||
$("#version").text(`(${data.version})`);
|
$("#version").text(`${data.version}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 拉取播放列表
|
// 拉取播放列表
|
||||||
@@ -82,6 +82,13 @@ $(function(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#playurl").on("click", () => {
|
||||||
|
var url = $("#music-url").val();
|
||||||
|
$.get(`/playurl?url=${url}`, function(data, status) {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function append_op_button_name(name) {
|
function append_op_button_name(name) {
|
||||||
append_op_button(name, name);
|
append_op_button(name, name);
|
||||||
}
|
}
|
||||||
|
|||||||
40
xiaomusic/static/debug.html
Normal file
40
xiaomusic/static/debug.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<title>Debug For XiaoMusic</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||||
|
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||||
|
<script src="/static/jquery-3.7.1.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var vConsole = new window.VConsole();
|
||||||
|
|
||||||
|
function postJSON() {
|
||||||
|
var data = $('#post-input').val();
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/debug_play_by_music_url',
|
||||||
|
data: data,
|
||||||
|
contentType: "application/json; charset=utf-8",
|
||||||
|
success: (err) => {
|
||||||
|
console.log("succ", res);
|
||||||
|
},
|
||||||
|
error: (res) => {
|
||||||
|
console.log("error", res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Debug For XiaoMusic</h1>
|
||||||
|
<textarea id="post-input" rows="10" cols="50" placeholder="粘贴json数据..."></textarea><br>
|
||||||
|
<button onclick="postJSON()">提交</button><br>
|
||||||
|
</body>
|
||||||
|
<footer>
|
||||||
|
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||||
|
</footer>
|
||||||
|
</html>
|
||||||
@@ -8,7 +8,11 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>小爱音箱操控面板<span id="version">(版本未知)</span></h2>
|
<h2>小爱音箱操控面板
|
||||||
|
(<a id="version" href="https://github.com/hanxi/xiaomusic/releases">
|
||||||
|
版本未知
|
||||||
|
</a>)
|
||||||
|
</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<div id="cmds">
|
<div id="cmds">
|
||||||
</div>
|
</div>
|
||||||
@@ -43,6 +47,12 @@
|
|||||||
<button id="play_music_list">播放列表歌曲</button>
|
<button id="play_music_list">播放列表歌曲</button>
|
||||||
<button id="del_music">删除选中歌曲</button>
|
<button id="del_music">删除选中歌曲</button>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<input id="music-url" type="text" placeholder="链接(http://ngcdn001.cnr.cn/live/zgzs/index.m3u8)"></input>
|
||||||
|
</div>
|
||||||
|
<button id="playurl">播放链接</button>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>小爱音箱设置面板<span id="version">(版本未知)</span></h2>
|
<h2>小爱音箱设置面板
|
||||||
|
(<a id="version" href="https://github.com/hanxi/xiaomusic/releases">
|
||||||
|
版本未知
|
||||||
|
</a>)
|
||||||
|
</h2>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="rows">
|
<div class="rows">
|
||||||
<label for="mi_did">MI_DID:</label>
|
<label for="mi_did">MI_DID:</label>
|
||||||
@@ -31,9 +35,12 @@
|
|||||||
<button onclick="location.href='/';">返回首页</button>
|
<button onclick="location.href='/';">返回首页</button>
|
||||||
<button id="get_music_list">获取歌单</button>
|
<button id="get_music_list">获取歌单</button>
|
||||||
<button id="save">保存</button>
|
<button id="save">保存</button>
|
||||||
|
<a class="button" href="/downloadlog" download="xiaomusic.txt">下载日志文件</a>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<a href="/static/m3u.html" target="_blank">m3u文件转换工具</a>
|
<a href="/static/m3u.html" target="_blank">m3u文件转换工具</a>
|
||||||
|
<hr>
|
||||||
|
<a href="/static/debug.html" target="_blank">调试工具</a>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ $(function(){
|
|||||||
// 拉取版本
|
// 拉取版本
|
||||||
$.get("/getversion", function(data, status) {
|
$.get("/getversion", function(data, status) {
|
||||||
console.log(data, status, data["version"]);
|
console.log(data, status, data["version"]);
|
||||||
$("#version").text(`(${data.version})`);
|
$("#version").text(`${data.version}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSelectOptions = (selectId, optionsList, selectedOption) => {
|
const updateSelectOptions = (selectId, optionsList, selectedOption) => {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
button {
|
.button {
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
button, .button {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -10,7 +14,7 @@ button {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: #008CBA;
|
background-color: #008CBA;
|
||||||
}
|
}
|
||||||
button:active {
|
button:active, .button:active {
|
||||||
font-weight:bold;
|
font-weight:bold;
|
||||||
background-color: #007CBA;
|
background-color: #007CBA;
|
||||||
transform: translateY(2px);
|
transform: translateY(2px);
|
||||||
|
|||||||
@@ -72,7 +72,12 @@ def validate_proxy(proxy_str: str) -> bool:
|
|||||||
|
|
||||||
# 模糊搜索
|
# 模糊搜索
|
||||||
def fuzzyfinder(user_input, collection):
|
def fuzzyfinder(user_input, collection):
|
||||||
return difflib.get_close_matches(user_input, collection, 10, cutoff=0.1)
|
return difflib.get_close_matches(user_input, collection, n=10, cutoff=0.1)
|
||||||
|
|
||||||
|
|
||||||
|
def find_best_match(user_input, collection, cutoff=0.6):
|
||||||
|
matches = difflib.get_close_matches(user_input, collection, n=1, cutoff=cutoff)
|
||||||
|
return matches[0] if matches else None
|
||||||
|
|
||||||
|
|
||||||
# 歌曲排序
|
# 歌曲排序
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -9,6 +10,7 @@ import re
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aiohttp import ClientSession, ClientTimeout
|
from aiohttp import ClientSession, ClientTimeout
|
||||||
@@ -29,9 +31,9 @@ from xiaomusic.const import (
|
|||||||
from xiaomusic.httpserver import StartHTTPServer
|
from xiaomusic.httpserver import StartHTTPServer
|
||||||
from xiaomusic.utils import (
|
from xiaomusic.utils import (
|
||||||
custom_sort_key,
|
custom_sort_key,
|
||||||
|
find_best_match,
|
||||||
fuzzyfinder,
|
fuzzyfinder,
|
||||||
get_local_music_duration,
|
get_local_music_duration,
|
||||||
get_random,
|
|
||||||
get_web_music_duration,
|
get_web_music_duration,
|
||||||
parse_cookie_string,
|
parse_cookie_string,
|
||||||
walk_to_depth,
|
walk_to_depth,
|
||||||
@@ -91,14 +93,8 @@ class XiaoMusic:
|
|||||||
# 关机定时器
|
# 关机定时器
|
||||||
self._stop_timer = None
|
self._stop_timer = None
|
||||||
|
|
||||||
# setup logger
|
# 初始化日志
|
||||||
logging.basicConfig(
|
self.setup_logger()
|
||||||
format=f"%(asctime)s [{__version__}] [%(levelname)s] %(message)s",
|
|
||||||
datefmt="[%X]",
|
|
||||||
)
|
|
||||||
self.log = logging.getLogger("xiaomusic")
|
|
||||||
self.log.setLevel(logging.DEBUG if config.verbose else logging.INFO)
|
|
||||||
self.log.debug(config)
|
|
||||||
|
|
||||||
# 尝试从设置里加载配置
|
# 尝试从设置里加载配置
|
||||||
self.try_init_setting()
|
self.try_init_setting()
|
||||||
@@ -109,7 +105,30 @@ class XiaoMusic:
|
|||||||
# 启动时初始化获取声音
|
# 启动时初始化获取声音
|
||||||
self.set_last_record("get_volume#")
|
self.set_last_record("get_volume#")
|
||||||
|
|
||||||
self.log.info("ffmpeg_location: %s", self.ffmpeg_location)
|
def setup_logger(self):
|
||||||
|
logging.basicConfig(
|
||||||
|
format=f"%(asctime)s [{__version__}] [%(levelname)s] %(message)s",
|
||||||
|
datefmt="[%X]",
|
||||||
|
)
|
||||||
|
|
||||||
|
log_file = self.config.log_file
|
||||||
|
log_path = os.path.dirname(log_file)
|
||||||
|
if not os.path.exists(log_path):
|
||||||
|
os.makedirs(log_path)
|
||||||
|
if os.path.exists(log_file):
|
||||||
|
os.remove(log_file)
|
||||||
|
handler = RotatingFileHandler(
|
||||||
|
self.config.log_file, maxBytes=10 * 1024 * 1024, backupCount=1
|
||||||
|
)
|
||||||
|
self.log = logging.getLogger("xiaomusic")
|
||||||
|
self.log.addHandler(handler)
|
||||||
|
self.log.setLevel(logging.DEBUG if self.config.verbose else logging.INFO)
|
||||||
|
debug_config = copy.deepcopy(self.config)
|
||||||
|
debug_config.account = "******"
|
||||||
|
debug_config.password = "******"
|
||||||
|
debug_config.httpauth_username = "******"
|
||||||
|
debug_config.httpauth_password = "******"
|
||||||
|
self.log.info(debug_config)
|
||||||
|
|
||||||
async def poll_latest_ask(self):
|
async def poll_latest_ask(self):
|
||||||
async with ClientSession() as session:
|
async with ClientSession() as session:
|
||||||
@@ -270,10 +289,13 @@ class XiaoMusic:
|
|||||||
await self.mina_service.text_to_speech(self.device_id, value)
|
await self.mina_service.text_to_speech(self.device_id, value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f"Execption {e}")
|
self.log.error(f"Execption {e}")
|
||||||
|
# 最大等8秒
|
||||||
|
sec = min(8, int(len(value) / 3))
|
||||||
|
await asyncio.sleep(sec)
|
||||||
self.log.debug(f"do_tts. cur_music:{self.cur_music}")
|
self.log.debug(f"do_tts. cur_music:{self.cur_music}")
|
||||||
if self._playing and not self.is_downloading():
|
if self._playing and not self.is_downloading():
|
||||||
# 继续播放歌曲
|
# 继续播放歌曲
|
||||||
|
self.log.info("继续播放歌曲")
|
||||||
await self.play()
|
await self.play()
|
||||||
|
|
||||||
async def do_set_volume(self, value):
|
async def do_set_volume(self, value):
|
||||||
@@ -285,8 +307,25 @@ class XiaoMusic:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f"Execption {e}")
|
self.log.error(f"Execption {e}")
|
||||||
|
|
||||||
|
async def get_if_xiaoai_is_playing(self):
|
||||||
|
playing_info = await self.mina_service.player_get_status(self.device_id)
|
||||||
|
# WTF xiaomi api
|
||||||
|
is_playing = (
|
||||||
|
json.loads(playing_info.get("data", {}).get("info", "{}")).get("status", -1)
|
||||||
|
== 1
|
||||||
|
)
|
||||||
|
return is_playing
|
||||||
|
|
||||||
|
async def stop_if_xiaoai_is_playing(self):
|
||||||
|
is_playing = await self.get_if_xiaoai_is_playing()
|
||||||
|
if is_playing:
|
||||||
|
# stop it
|
||||||
|
ret = await self.mina_service.player_stop(self.device_id)
|
||||||
|
self.log.debug(f"force_stop_xiaoai player_stop ret:{ret}")
|
||||||
|
|
||||||
async def force_stop_xiaoai(self):
|
async def force_stop_xiaoai(self):
|
||||||
await self.mina_service.player_stop(self.device_id)
|
ret = await self.mina_service.player_pause(self.device_id)
|
||||||
|
self.log.debug(f"force_stop_xiaoai player_pause ret:{ret}")
|
||||||
|
|
||||||
# 是否在下载中
|
# 是否在下载中
|
||||||
def is_downloading(self):
|
def is_downloading(self):
|
||||||
@@ -450,6 +489,7 @@ class XiaoMusic:
|
|||||||
if not self.config.music_list_json:
|
if not self.config.music_list_json:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._all_radio = {}
|
||||||
music_list = json.loads(self.config.music_list_json)
|
music_list = json.loads(self.config.music_list_json)
|
||||||
try:
|
try:
|
||||||
for item in music_list:
|
for item in music_list:
|
||||||
@@ -570,7 +610,7 @@ class XiaoMusic:
|
|||||||
self.polling_event.clear() # stop polling when processing the question
|
self.polling_event.clear() # stop polling when processing the question
|
||||||
query = new_record.get("query", "").strip()
|
query = new_record.get("query", "").strip()
|
||||||
ctrl_panel = new_record.get("ctrl_panel", False)
|
ctrl_panel = new_record.get("ctrl_panel", False)
|
||||||
self.log.debug("收到消息:%s 控制面板:%s", query, ctrl_panel)
|
self.log.info("收到消息:%s 控制面板:%s", query, ctrl_panel)
|
||||||
|
|
||||||
# 匹配命令
|
# 匹配命令
|
||||||
opvalue, oparg = self.match_cmd(query, ctrl_panel)
|
opvalue, oparg = self.match_cmd(query, ctrl_panel)
|
||||||
@@ -584,8 +624,26 @@ class XiaoMusic:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
||||||
|
|
||||||
|
# 检查是否匹配到完全一样的指令
|
||||||
|
def check_full_match_cmd(self, query, ctrl_panel):
|
||||||
|
if query in self.config.key_match_order:
|
||||||
|
opkey = query
|
||||||
|
opvalue = self.config.key_word_dict.get(opkey)
|
||||||
|
if ctrl_panel or self._playing:
|
||||||
|
return opvalue
|
||||||
|
else:
|
||||||
|
if not self.active_cmd or opvalue in self.active_cmd:
|
||||||
|
return opvalue
|
||||||
|
return None
|
||||||
|
|
||||||
# 匹配命令
|
# 匹配命令
|
||||||
def match_cmd(self, query, ctrl_panel):
|
def match_cmd(self, query, ctrl_panel):
|
||||||
|
# 优先处理完全匹配
|
||||||
|
opvalue = self.check_full_match_cmd(query, ctrl_panel)
|
||||||
|
if opvalue:
|
||||||
|
self.log.info(f"完全匹配指令. query:{query} opvalue:{opvalue}")
|
||||||
|
return (opvalue, "")
|
||||||
|
|
||||||
for opkey in self.config.key_match_order:
|
for opkey in self.config.key_match_order:
|
||||||
patternarg = rf"(.*){opkey}(.*)"
|
patternarg = rf"(.*){opkey}(.*)"
|
||||||
# 匹配参数
|
# 匹配参数
|
||||||
@@ -603,20 +661,18 @@ class XiaoMusic:
|
|||||||
argafter,
|
argafter,
|
||||||
)
|
)
|
||||||
oparg = argafter
|
oparg = argafter
|
||||||
|
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
||||||
|
oparg = argpre
|
||||||
opvalue = self.config.key_word_dict.get(opkey)
|
opvalue = self.config.key_word_dict.get(opkey)
|
||||||
if not ctrl_panel and not self._playing:
|
if not ctrl_panel and not self._playing:
|
||||||
if self.active_cmd and opvalue not in self.active_cmd:
|
if self.active_cmd and opvalue not in self.active_cmd:
|
||||||
self.log.debug(f"不在激活命令中 {opvalue}")
|
self.log.ifno(f"不在激活命令中 {opvalue}")
|
||||||
continue
|
continue
|
||||||
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
self.log.info(f"匹配到指令. opkey:{opkey} opvalue:{opvalue} oparg:{oparg}")
|
||||||
oparg = argpre
|
|
||||||
self.log.info(
|
|
||||||
"匹配到指令. opkey:%s opvalue:%s oparg:%s", opkey, opvalue, oparg
|
|
||||||
)
|
|
||||||
return (opvalue, oparg)
|
return (opvalue, oparg)
|
||||||
if self._playing:
|
if self._playing:
|
||||||
self.log.info("未匹配到指令,自动停止")
|
self.log.info("未匹配到指令,自动停止")
|
||||||
return ("stop", {})
|
return ("stop", "notts")
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
# 判断是否播放下一首歌曲
|
# 判断是否播放下一首歌曲
|
||||||
@@ -630,29 +686,10 @@ class XiaoMusic:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _play_by_music_url(self, device_id, url):
|
async def play_url(self, **kwargs):
|
||||||
audio_id = get_random(30)
|
url = kwargs.get("arg1", "")
|
||||||
audio_type = ""
|
|
||||||
if self.config.hardware in ["LX04", "X10A", "X08A"]:
|
|
||||||
audio_type = "MUSIC"
|
|
||||||
music = {
|
|
||||||
"payload": {
|
|
||||||
"audio_items": [
|
|
||||||
{"item_id": {"audio_id": audio_id}, "stream": {"url": url}}
|
|
||||||
],
|
|
||||||
"audio_type": audio_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await self.mina_service.ubus_request(
|
|
||||||
device_id,
|
|
||||||
"player_play_music",
|
|
||||||
"mediaplayer",
|
|
||||||
{"startaudioid": audio_id, "music": json.dumps(music)},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def play_url(self, url):
|
|
||||||
if self.config.use_music_api:
|
if self.config.use_music_api:
|
||||||
ret = await self._play_by_music_url(self.device_id, url)
|
ret = await self.mina_service.play_by_music_url(self.device_id, url, _type=2)
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"play_url play_by_music_url {self.config.hardware}. ret:{ret} url:{url}"
|
f"play_url play_by_music_url {self.config.hardware}. ret:{ret} url:{url}"
|
||||||
)
|
)
|
||||||
@@ -661,6 +698,22 @@ class XiaoMusic:
|
|||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"play_url play_by_url {self.config.hardware}. ret:{ret} url:{url}"
|
f"play_url play_by_url {self.config.hardware}. ret:{ret} url:{url}"
|
||||||
)
|
)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def find_real_music_name(self, name):
|
||||||
|
if not self.config.enable_fuzzy_match:
|
||||||
|
self.log.debug("没开启模糊匹配")
|
||||||
|
return name
|
||||||
|
|
||||||
|
all_music_list = list(self._all_music.keys())
|
||||||
|
real_name = find_best_match(
|
||||||
|
name, all_music_list, cutoff=self.config.fuzzy_match_cutoff
|
||||||
|
)
|
||||||
|
if real_name:
|
||||||
|
self.log.info(f"根据【{name}】找到歌曲【{real_name}】")
|
||||||
|
return real_name
|
||||||
|
self.log.info(f"没找到歌曲【{name}】")
|
||||||
|
return name
|
||||||
|
|
||||||
# 播放本地歌曲
|
# 播放本地歌曲
|
||||||
async def playlocal(self, **kwargs):
|
async def playlocal(self, **kwargs):
|
||||||
@@ -675,6 +728,7 @@ class XiaoMusic:
|
|||||||
self.log.info(f"playlocal. name:{name}")
|
self.log.info(f"playlocal. name:{name}")
|
||||||
|
|
||||||
# 本地歌曲不存在时下载
|
# 本地歌曲不存在时下载
|
||||||
|
name = self.find_real_music_name(name)
|
||||||
if not self.is_music_exist(name):
|
if not self.is_music_exist(name):
|
||||||
await self.do_tts(f"本地不存在歌曲{name}")
|
await self.do_tts(f"本地不存在歌曲{name}")
|
||||||
return
|
return
|
||||||
@@ -687,7 +741,7 @@ class XiaoMusic:
|
|||||||
sec, url = await self.get_music_sec_url(name)
|
sec, url = await self.get_music_sec_url(name)
|
||||||
self.log.info(f"播放 {url}")
|
self.log.info(f"播放 {url}")
|
||||||
await self.force_stop_xiaoai()
|
await self.force_stop_xiaoai()
|
||||||
await self.play_url(url)
|
await self.play_url(arg1=url)
|
||||||
self.log.info("已经开始播放了")
|
self.log.info("已经开始播放了")
|
||||||
# 设置下一首歌曲的播放定时器
|
# 设置下一首歌曲的播放定时器
|
||||||
await self.set_next_music_timeout(sec)
|
await self.set_next_music_timeout(sec)
|
||||||
@@ -710,12 +764,13 @@ class XiaoMusic:
|
|||||||
self.log.info("play. search_key:%s name:%s", search_key, name)
|
self.log.info("play. search_key:%s name:%s", search_key, name)
|
||||||
|
|
||||||
# 本地歌曲不存在时下载
|
# 本地歌曲不存在时下载
|
||||||
|
name = self.find_real_music_name(name)
|
||||||
if not self.is_music_exist(name):
|
if not self.is_music_exist(name):
|
||||||
if self.config.disable_download:
|
if self.config.disable_download:
|
||||||
await self.do_tts(f"本地不存在歌曲{name}")
|
await self.do_tts(f"本地不存在歌曲{name}")
|
||||||
return
|
return
|
||||||
await self.download(search_key, name)
|
await self.download(search_key, name)
|
||||||
self.log.info("正在下载中 %s", search_key + ":" + name)
|
self.log.info(f"正在下载中 {search_key} {name}")
|
||||||
await self.download_proc.wait()
|
await self.download_proc.wait()
|
||||||
# 把文件插入到播放列表里
|
# 把文件插入到播放列表里
|
||||||
self.add_download_music(name)
|
self.add_download_music(name)
|
||||||
@@ -771,10 +826,27 @@ class XiaoMusic:
|
|||||||
self.log.error(f"del ${filename} failed")
|
self.log.error(f"del ${filename} failed")
|
||||||
self._gen_all_music_list()
|
self._gen_all_music_list()
|
||||||
|
|
||||||
|
def find_real_music_list_name(self, list_name):
|
||||||
|
if not self.config.enable_fuzzy_match:
|
||||||
|
self.log.debug("没开启模糊匹配")
|
||||||
|
return list_name
|
||||||
|
|
||||||
|
# 模糊搜一个播放列表
|
||||||
|
real_name = find_best_match(
|
||||||
|
list_name, self._music_list, cutoff=self.config.fuzzy_match_cutoff
|
||||||
|
)
|
||||||
|
if real_name:
|
||||||
|
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}】")
|
||||||
|
list_name = real_name
|
||||||
|
self.log.info(f"没找到播放列表【{list_name}】")
|
||||||
|
return list_name
|
||||||
|
|
||||||
# 播放一个播放列表
|
# 播放一个播放列表
|
||||||
async def play_music_list(self, **kwargs):
|
async def play_music_list(self, **kwargs):
|
||||||
parts = kwargs.get("arg1").split("|")
|
parts = kwargs.get("arg1").split("|")
|
||||||
list_name = parts[0]
|
list_name = parts[0]
|
||||||
|
|
||||||
|
list_name = self.find_real_music_list_name(list_name)
|
||||||
if list_name not in self._music_list:
|
if list_name not in self._music_list:
|
||||||
await self.do_tts(f"播放列表{list_name}不存在")
|
await self.do_tts(f"播放列表{list_name}不存在")
|
||||||
return
|
return
|
||||||
@@ -792,6 +864,9 @@ class XiaoMusic:
|
|||||||
|
|
||||||
async def stop(self, **kwargs):
|
async def stop(self, **kwargs):
|
||||||
self._playing = False
|
self._playing = False
|
||||||
|
if kwargs.get("arg1", "") != "notts":
|
||||||
|
if self.config.stop_tts_msg:
|
||||||
|
await self.do_tts(self.config.stop_tts_msg)
|
||||||
if self._next_timer:
|
if self._next_timer:
|
||||||
self._next_timer.cancel()
|
self._next_timer.cancel()
|
||||||
self.log.info("定时器已取消")
|
self.log.info("定时器已取消")
|
||||||
@@ -806,12 +881,12 @@ class XiaoMusic:
|
|||||||
async def _do_stop():
|
async def _do_stop():
|
||||||
await asyncio.sleep(minute * 60)
|
await asyncio.sleep(minute * 60)
|
||||||
try:
|
try:
|
||||||
await self.stop()
|
await self.stop(arg1="notts")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
||||||
|
|
||||||
self._stop_timer = asyncio.ensure_future(_do_stop())
|
self._stop_timer = asyncio.ensure_future(_do_stop())
|
||||||
self.log.info(f"{minute}分钟后将关机")
|
await self.do_tts(f"收到,{minute}分钟后将关机")
|
||||||
|
|
||||||
async def set_volume(self, **kwargs):
|
async def set_volume(self, **kwargs):
|
||||||
value = kwargs.get("arg1", 0)
|
value = kwargs.get("arg1", 0)
|
||||||
@@ -821,7 +896,7 @@ class XiaoMusic:
|
|||||||
playing_info = await self.mina_service.player_get_status(self.device_id)
|
playing_info = await self.mina_service.player_get_status(self.device_id)
|
||||||
self.log.debug("get_volume. playing_info:%s", playing_info)
|
self.log.debug("get_volume. playing_info:%s", playing_info)
|
||||||
self._volume = json.loads(playing_info.get("data", {}).get("info", "{}")).get(
|
self._volume = json.loads(playing_info.get("data", {}).get("info", "{}")).get(
|
||||||
"volume", 5
|
"volume", 0
|
||||||
)
|
)
|
||||||
self.log.info("get_volume. volume:%s", self._volume)
|
self.log.info("get_volume. volume:%s", self._volume)
|
||||||
|
|
||||||
@@ -936,3 +1011,15 @@ class XiaoMusic:
|
|||||||
self.new_record_event.set()
|
self.new_record_event.set()
|
||||||
result = await future
|
result = await future
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def debug_play_by_music_url(self, arg1=None):
|
||||||
|
if arg1 is None:
|
||||||
|
arg1 = {}
|
||||||
|
data = arg1
|
||||||
|
self.log.info(f"debug_play_by_music_url: {data}")
|
||||||
|
return await self.mina_service.ubus_request(
|
||||||
|
self.device_id,
|
||||||
|
"player_play_music",
|
||||||
|
"mediaplayer",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user