mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-10 15:28:15 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3658e0368 | ||
|
|
d28f4e1473 | ||
|
|
1efdcf2144 | ||
|
|
0f1516ae7e | ||
|
|
f111d67819 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +0,0 @@
|
|||||||
github: [hanxi]
|
|
||||||
custom: ['https://afdian.net/a/imhanxi']
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
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
|
||||||
|
|||||||
167
README.md
167
README.md
@@ -1,20 +1,7 @@
|
|||||||
# xiaomusic
|
# xiaomusic
|
||||||
[](https://github.com/hanxi/xiaomusic)
|
|
||||||
[](https://hub.docker.com/r/hanxi/xiaomusic)
|
|
||||||
[](https://hub.docker.com/r/hanxi/xiaomusic)
|
|
||||||
[](https://pypi.org/project/xiaomusic/)
|
|
||||||
[](https://pypi.org/project/xiaomusic/)
|
|
||||||
[](https://pypi.org/project/xiaomusic/)
|
|
||||||
[](https://github.com/hanxi/xiaomusic/releases)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
|
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
|
||||||
|
|
||||||
<https://github.com/hanxi/xiaomusic>
|
|
||||||
|
|
||||||
> 初次安装遇到问题请查阅 <https://github.com/hanxi/xiaomusic/issues/99> 上是否已经有解决办法。
|
|
||||||
|
|
||||||
## 最简配置运行
|
## 最简配置运行
|
||||||
|
|
||||||
已经支持在 web 页面配置其他参数,docker compose 配置如下:
|
已经支持在 web 页面配置其他参数,docker compose 配置如下:
|
||||||
@@ -32,79 +19,10 @@ 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端口
|
|
||||||
|
|
||||||
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
xiaomusic:
|
|
||||||
image: hanxi/xiaomusic
|
|
||||||
container_name: xiaomusic
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 5678:5678
|
|
||||||
volumes:
|
|
||||||
- ./music:/app/music
|
|
||||||
environment:
|
|
||||||
MI_USER: '小米账号'
|
|
||||||
MI_PASS: '小米密码'
|
|
||||||
XIAOMUSIC_VERBOSE: 'true'
|
|
||||||
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
|
||||||
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 下载依赖
|
||||||
@@ -139,37 +57,21 @@ pdm run xiaomusic.py
|
|||||||
|
|
||||||
> 隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会播放小猪佩奇的故事。
|
> 隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会播放小猪佩奇的故事。
|
||||||
|
|
||||||
## 已测试支持的设备
|
## 已测试设备
|
||||||
|
|
||||||
| 型号 | 名称 |
|
|
||||||
| ---- | ---------------------------------------------------------------------------------------------- |
|
|
||||||
| L06A | [小爱音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l06a) |
|
|
||||||
| L07A | [Redmi小爱音箱 Play](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l7a) |
|
|
||||||
| S12 | [小米AI音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.s12) |
|
|
||||||
| S12A | - |
|
|
||||||
| LX5A | [小爱音箱 万能遥控版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx5a) |
|
|
||||||
| LX05 | [小爱音箱Play(2019款)](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx05) |
|
|
||||||
| L16A | [Xiaomi Sound](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l16a) |
|
|
||||||
| 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>
|
|
||||||
|
|
||||||
> 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。
|
|
||||||
|
|
||||||
|
```txt
|
||||||
|
"L07A": ("5-1", "5-5"), # Redmi小爱音箱Play(l7a)
|
||||||
|
````
|
||||||
## 支持音乐格式
|
## 支持音乐格式
|
||||||
|
|
||||||
- mp3
|
- mp3
|
||||||
- flac
|
- flac
|
||||||
- wav
|
|
||||||
- ape
|
|
||||||
- ogg
|
|
||||||
|
|
||||||
> 本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式的。
|
> 本地音乐会搜索 mp3 和 flac 格式的文件,下载的歌曲是 mp3 格式的。
|
||||||
> 已知 L05B L05C 不支持 flac 格式。
|
|
||||||
|
## 其他参数
|
||||||
|
|
||||||
|
- XIAOMUSIC_ACTIVE_CMD 环境变量,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。
|
||||||
|
|
||||||
## 在 Docker 里使用
|
## 在 Docker 里使用
|
||||||
|
|
||||||
@@ -260,26 +162,6 @@ services:
|
|||||||
XIAOMUSIC_HOSTNAME: '192.168.2.5'
|
XIAOMUSIC_HOSTNAME: '192.168.2.5'
|
||||||
```
|
```
|
||||||
|
|
||||||
如果想让 setting.json 文件不存储到 music 目录,可以这样配,下面的示例会把 setting.json 文件放到容器的 /app/conf 目录且映射到本地的 ./conf 目录:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
xiaomusic:
|
|
||||||
image: hanxi/xiaomusic
|
|
||||||
container_name: xiaomusic
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 8090:8090
|
|
||||||
volumes:
|
|
||||||
- ./music:/app/music
|
|
||||||
- ./conf:/app/conf
|
|
||||||
environment:
|
|
||||||
MI_USER: '小米账号'
|
|
||||||
MI_PASS: '小米密码'
|
|
||||||
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
|
||||||
XIAOMUSIC_CONF_PATH: '/app/conf'
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## 简易的控制面板
|
## 简易的控制面板
|
||||||
|
|
||||||
@@ -287,14 +169,11 @@ services:
|
|||||||
|
|
||||||
- ip 是 XIAOMUSIC_HOSTNAME 设置的
|
- ip 是 XIAOMUSIC_HOSTNAME 设置的
|
||||||
- 8090 是默认端口
|
- 8090 是默认端口
|
||||||
- 支持功能
|
- 新功能
|
||||||
- 显示正在播放的歌曲
|
- 显示正在播放的歌曲
|
||||||
- 模糊搜索本地歌曲
|
- 模糊搜索本地歌曲
|
||||||
- 播放列表
|
|
||||||
- 删除歌曲
|
|
||||||
- 设置页面
|
- 设置页面
|
||||||
- 配置网络歌单
|
|
||||||
- 日志文件下载
|
|
||||||
|
|
||||||
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
|
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
|
||||||
- MI_USER
|
- MI_USER
|
||||||
@@ -307,35 +186,22 @@ services:
|
|||||||
- XIAOMUSIC_SEARCH
|
- XIAOMUSIC_SEARCH
|
||||||
- XIAOMUSIC_PROXY
|
- XIAOMUSIC_PROXY
|
||||||
|
|
||||||
## 网络歌单功能
|
|
||||||
|
|
||||||
可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,同时配备了 m3u 文件格式转换工具,可以很方便的把 m3u 电台文件转换成网络歌单格式的 json 文件,具体用法见 <https://github.com/hanxi/xiaomusic/issues/78>
|
|
||||||
|
|
||||||
> 欢迎有想法的朋友们制作更多的歌单转换工具。
|
|
||||||
|
|
||||||
## 更多其他可选配置
|
## 更多其他可选配置
|
||||||
|
|
||||||
- XIAOMUSIC_ACTIVE_CMD 环境变量,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 <https://github.com/hanxi/xiaomusic/pull/43>
|
- XIAOMUSIC_ACTIVE_CMD 配置唤醒命令,具体见 <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_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,见 <https://github.com/hanxi/xiaomusic/issues/82>
|
- XIAOMUSIC_VERBOSE 设置为 true 时开启 debug 日志,用于排查问题
|
||||||
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,v0.1.86 之后的版本可以不用设置。
|
|
||||||
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
|
||||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
|
||||||
- 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>
|
|
||||||
|
|
||||||
## 讨论区
|
## 讨论区
|
||||||
|
|
||||||
- [点击链接加入QQ频道【xiaomusic】](https://pd.qq.com/s/e2jybz0ss)
|
- [点击链接加入QQ频道【xiaomusic】](https://pd.qq.com/s/e2jybz0ss)
|
||||||
- [点击链接加入群聊【xiaomusic】 604526973](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=13St5PLVcTxYlWTAs_iAawazjtdD1l-a&authKey=dJWEpaT2fDBDpdUUOWj%2FLt6NS1ePBfShDfz7a6seNURi05VvVnAGQzXF%2FM%2F5HgIm&noverify=0&group_code=604526973)
|
- [点击链接加入群聊【xiaomusic】 604526973](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=13St5PLVcTxYlWTAs_iAawazjtdD1l-a&authKey=dJWEpaT2fDBDpdUUOWj%2FLt6NS1ePBfShDfz7a6seNURi05VvVnAGQzXF%2FM%2F5HgIm&noverify=0&group_code=604526973)
|
||||||
- <https://github.com/hanxi/xiaomusic/issues>
|
- https://github.com/hanxi/xiaomusic/issues
|
||||||
- [微信群二维码](https://github.com/hanxi/xiaomusic/issues/86)
|
|
||||||
|
|
||||||
## 感谢
|
## 感谢
|
||||||
|
|
||||||
@@ -353,7 +219,4 @@ services:
|
|||||||
[](https://star-history.com/#hanxi/xiaomusic&Date)
|
[](https://star-history.com/#hanxi/xiaomusic&Date)
|
||||||
|
|
||||||
## 赞赏
|
## 赞赏
|
||||||
|
谢谢就够了
|
||||||
- 爱发电 <https://afdian.net/a/imhanxi>
|
|
||||||
- 点个 Star ⭐
|
|
||||||
- 谢谢 ❤️
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"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,41 +4,14 @@
|
|||||||
# https://github.com/yt-dlp/yt-dlp#dependencies
|
# https://github.com/yt-dlp/yt-dlp#dependencies
|
||||||
|
|
||||||
# 判断系统架构
|
# 判断系统架构
|
||||||
arch=$(uname -m)
|
arch=$(arch)
|
||||||
|
|
||||||
# 输出架构信息
|
pkg=ffmpeg-master-latest-linuxarm64-gpl
|
||||||
echo "当前系统架构是:$arch"
|
if [[ "${arch}" == "x86_64" ]]; then
|
||||||
|
|
||||||
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
|
||||||
install_from_build "$pkg"
|
fi
|
||||||
;;
|
|
||||||
arm64 | aarch64)
|
#export ALL_PROXY=http://192.168.2.5:8080
|
||||||
echo "64位 ARM 架构"
|
wget https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/$pkg.tar.xz
|
||||||
pkg=ffmpeg-master-latest-linuxarm64-gpl
|
tar -xvJf $pkg.tar.xz
|
||||||
install_from_build "$pkg"
|
mv $pkg ffmpeg
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "未知架构 $arch"
|
|
||||||
install_from_apt
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|||||||
61
pdm.lock
generated
61
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:e7455b13bf13306ccf5ad11781191edb62991d9fbe8f8ce1e61a2f35c713cc2a"
|
content_hash = "sha256:813253734c7d7835a76cd87fe8fe0329e02ad067f535aee6a9e11cb106569dd2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohttp"
|
name = "aiohttp"
|
||||||
@@ -519,7 +519,7 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miservice-fork"
|
name = "miservice-fork"
|
||||||
version = "2.6.1"
|
version = "2.5.0"
|
||||||
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.1-py3-none-any.whl", hash = "sha256:9b2cc4208486bbbf788d1bde6e2cbc70f241ce10db4dca6f918076a2d2942a39"},
|
{file = "miservice_fork-2.5.0-py3-none-any.whl", hash = "sha256:97b6360ea53c34fe035ac9d94e8705f305b8fa7fc2b44a7aea182449a76cb622"},
|
||||||
{file = "miservice_fork-2.6.1.tar.gz", hash = "sha256:1702281e1e9827958eb3e82bc3242cd013c018e9aa1de8509b4805b5ccf5e60c"},
|
{file = "miservice_fork-2.5.0.tar.gz", hash = "sha256:8ca2d370d5b32f7e330add38aa1912d734aefa7880f16cef9eac110a5a3029e2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -632,8 +632,8 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.31.0"
|
version = "2.32.3"
|
||||||
requires_python = ">=3.7"
|
requires_python = ">=3.8"
|
||||||
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.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -662,28 +662,27 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.5.0"
|
version = "0.4.9"
|
||||||
requires_python = ">=3.7"
|
requires_python = ">=3.7"
|
||||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"},
|
{file = "ruff-0.4.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b262ed08d036ebe162123170b35703aaf9daffecb698cd367a8d585157732991"},
|
||||||
{file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"},
|
{file = "ruff-0.4.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:98ec2775fd2d856dc405635e5ee4ff177920f2141b8e2d9eb5bd6efd50e80317"},
|
||||||
{file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4555056049d46d8a381f746680db1c46e67ac3b00d714606304077682832998e"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e91175fbe48f8a2174c9aad70438fe9cb0a5732c4159b2a10a3565fea2d94cde"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8e7b95673f22e0efd3571fb5b0cf71a5eaaa3cc8a776584f3b2cc878e46bff"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2d45ddc6d82e1190ea737341326ecbc9a61447ba331b0a8962869fcada758505"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78de3fdb95c4af084087628132336772b1c5044f6e710739d440fc0bccf4d321"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06b60f91bfa5514bb689b500a25ba48e897d18fea14dce14b48a0c40d1635893"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"},
|
{file = "ruff-0.4.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88bffe9c6a454bf8529f9ab9091c99490578a593cc9f9822b7fc065ee0712a06"},
|
||||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"},
|
{file = "ruff-0.4.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:673bddb893f21ab47a8334c8e0ea7fd6598ecc8e698da75bcd12a7b9d0a3206e"},
|
||||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"},
|
{file = "ruff-0.4.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8c1aff58c31948cc66d0b22951aa19edb5af0a3af40c936340cd32a8b1ab7438"},
|
||||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"},
|
{file = "ruff-0.4.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:784d3ec9bd6493c3b720a0b76f741e6c2d7d44f6b2be87f5eef1ae8cc1d54c84"},
|
||||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"},
|
{file = "ruff-0.4.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:732dd550bfa5d85af8c3c6cbc47ba5b67c6aed8a89e2f011b908fc88f87649db"},
|
||||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"},
|
{file = "ruff-0.4.9-py3-none-win32.whl", hash = "sha256:8064590fd1a50dcf4909c268b0e7c2498253273309ad3d97e4a752bb9df4f521"},
|
||||||
{file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"},
|
{file = "ruff-0.4.9-py3-none-win_amd64.whl", hash = "sha256:e0a22c4157e53d006530c902107c7f550b9233e9706313ab57b892d7197d8e52"},
|
||||||
{file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"},
|
{file = "ruff-0.4.9-py3-none-win_arm64.whl", hash = "sha256:5d5460f789ccf4efd43f265a58538a2c24dbce15dbf560676e430375f20a8198"},
|
||||||
{file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"},
|
{file = "ruff-0.4.9.tar.gz", hash = "sha256:f1cb0828ac9533ba0135d148d214e284711ede33640465e706772645483427e3"},
|
||||||
{file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -832,7 +831,7 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yt-dlp"
|
name = "yt-dlp"
|
||||||
version = "2024.5.30.232720.dev0"
|
version = "2024.6.17.232743.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 +840,11 @@ dependencies = [
|
|||||||
"certifi",
|
"certifi",
|
||||||
"mutagen",
|
"mutagen",
|
||||||
"pycryptodomex",
|
"pycryptodomex",
|
||||||
"requests<3,>=2.31.0",
|
"requests<3,>=2.32.2",
|
||||||
"urllib3<3,>=1.26.17",
|
"urllib3<3,>=1.26.17",
|
||||||
"websockets>=12.0",
|
"websockets>=12.0",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "yt_dlp-2024.5.30.232720.dev0-py3-none-any.whl", hash = "sha256:d6e563a2923807392325722028e7792e35affb694a505617b008195d0d212d2c"},
|
{file = "yt_dlp-2024.6.17.232743.dev0-py3-none-any.whl", hash = "sha256:dd6e7e194b96e778691f58a0cb6b42956cf956b22f6bb1a12bdef5ab3ac0c9ad"},
|
||||||
{file = "yt_dlp-2024.5.30.232720.dev0.tar.gz", hash = "sha256:9e2b177c5b13ea6f54cee1c56a69dd7832d506fba73a2247c6470e7d1952f959"},
|
{file = "yt_dlp-2024.6.17.232743.dev0.tar.gz", hash = "sha256:2f6f44eff755a7b051cdcd3c4375771033dbeb64d6164351022efdc67cce0c52"},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "xiaomusic"
|
name = "xiaomusic"
|
||||||
version = "0.1.87"
|
version = "0.1.63"
|
||||||
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,12 +19,6 @@ 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.1 \
|
miservice-fork==2.5.0 \
|
||||||
--hash=sha256:1702281e1e9827958eb3e82bc3242cd013c018e9aa1de8509b4805b5ccf5e60c \
|
--hash=sha256:8ca2d370d5b32f7e330add38aa1912d734aefa7880f16cef9eac110a5a3029e2 \
|
||||||
--hash=sha256:9b2cc4208486bbbf788d1bde6e2cbc70f241ce10db4dca6f918076a2d2942a39
|
--hash=sha256:97b6360ea53c34fe035ac9d94e8705f305b8fa7fc2b44a7aea182449a76cb622
|
||||||
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.31.0 \
|
requests==2.32.3 \
|
||||||
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
|
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
||||||
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
|
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
|
||||||
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.5.30.232720.dev0 \
|
yt-dlp==2024.6.17.232743.dev0 \
|
||||||
--hash=sha256:9e2b177c5b13ea6f54cee1c56a69dd7832d506fba73a2247c6470e7d1952f959 \
|
--hash=sha256:2f6f44eff755a7b051cdcd3c4375771033dbeb64d6164351022efdc67cce0c52 \
|
||||||
--hash=sha256:d6e563a2923807392325722028e7792e35affb694a505617b008195d0d212d2c
|
--hash=sha256:dd6e7e194b96e778691f58a0cb6b42956cf956b22f6bb1a12bdef5ab3ac0c9ad
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.87"
|
__version__ = "0.1.63"
|
||||||
|
|||||||
@@ -1,21 +1,9 @@
|
|||||||
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()
|
||||||
@@ -57,8 +45,6 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,18 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
from xiaomusic.utils import validate_proxy
|
from xiaomusic.utils import validate_proxy
|
||||||
|
|
||||||
# 默认口令
|
LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}×tamp={timestamp}&limit=2"
|
||||||
DEFAULT_KEY_WORD_DICT = {
|
COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={user_id}"
|
||||||
|
|
||||||
|
KEY_WORD_DICT = {
|
||||||
"播放歌曲": "play",
|
"播放歌曲": "play",
|
||||||
"播放本地歌曲": "playlocal",
|
"放歌曲": "play",
|
||||||
"关机": "stop",
|
|
||||||
"下一首": "play_next",
|
"下一首": "play_next",
|
||||||
"单曲循环": "set_play_type_one",
|
"单曲循环": "set_play_type_one",
|
||||||
"全部循环": "set_play_type_all",
|
"全部循环": "set_play_type_all",
|
||||||
"随机播放": "random_play",
|
"随机播放": "random_play",
|
||||||
|
"关机": "stop",
|
||||||
|
"停止播放": "stop",
|
||||||
"分钟后关机": "stop_after_minute",
|
"分钟后关机": "stop_after_minute",
|
||||||
"播放列表": "play_music_list",
|
"播放列表": "play_music_list",
|
||||||
"刷新列表": "gen_music_list",
|
"刷新列表": "gen_music_list",
|
||||||
@@ -28,21 +31,30 @@ KEY_WORD_ARG_BEFORE_DICT = {
|
|||||||
"分钟后关机": True,
|
"分钟后关机": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 口令匹配优先级
|
# 匹配优先级
|
||||||
DEFAULT_KEY_MATCH_ORDER = [
|
KEY_MATCH_ORDER = [
|
||||||
"set_volume#",
|
"set_volume#",
|
||||||
"get_volume#",
|
"get_volume#",
|
||||||
"分钟后关机",
|
"分钟后关机",
|
||||||
"播放歌曲",
|
"播放歌曲",
|
||||||
|
"放歌曲",
|
||||||
"下一首",
|
"下一首",
|
||||||
"单曲循环",
|
"单曲循环",
|
||||||
"全部循环",
|
"全部循环",
|
||||||
"随机播放",
|
"随机播放",
|
||||||
"关机",
|
"关机",
|
||||||
|
"停止播放",
|
||||||
"刷新列表",
|
"刷新列表",
|
||||||
"播放列表",
|
"播放列表",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SUPPORT_MUSIC_TYPE = [
|
||||||
|
".mp3",
|
||||||
|
".flac",
|
||||||
|
".wav",
|
||||||
|
".ape",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Config:
|
class Config:
|
||||||
@@ -61,9 +73,7 @@ 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(
|
active_cmd: str = os.getenv("XIAOMUSIC_ACTIVE_CMD", "play,random_play")
|
||||||
"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 = (
|
||||||
@@ -73,46 +83,10 @@ class Config:
|
|||||||
httpauth_password: str = os.getenv("XIAOMUSIC_HTTPAUTH_PASSWORD", "admin")
|
httpauth_password: str = os.getenv("XIAOMUSIC_HTTPAUTH_PASSWORD", "admin")
|
||||||
music_list_url: str = os.getenv("XIAOMUSIC_MUSIC_LIST_URL", "")
|
music_list_url: str = os.getenv("XIAOMUSIC_MUSIC_LIST_URL", "")
|
||||||
music_list_json: str = os.getenv("XIAOMUSIC_MUSIC_LIST_JSON", "")
|
music_list_json: str = os.getenv("XIAOMUSIC_MUSIC_LIST_JSON", "")
|
||||||
disable_download: bool = (
|
|
||||||
os.getenv("XIAOMUSIC_DISABLE_DOWNLOAD", "false").lower() == "true"
|
|
||||||
)
|
|
||||||
key_word_dict = DEFAULT_KEY_WORD_DICT.copy()
|
|
||||||
key_match_order = DEFAULT_KEY_MATCH_ORDER.copy()
|
|
||||||
use_music_api: bool = (
|
|
||||||
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):
|
|
||||||
for key in keys.split(","):
|
|
||||||
self.key_word_dict[key] = action
|
|
||||||
if key not in self.key_match_order:
|
|
||||||
self.key_match_order.append(key)
|
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if self.proxy:
|
if self.proxy:
|
||||||
validate_proxy(self.proxy)
|
validate_proxy(self.proxy)
|
||||||
self.append_keyword(self.keywords_playlocal, "playlocal")
|
|
||||||
self.append_keyword(self.keywords_play, "play")
|
|
||||||
self.append_keyword(self.keywords_stop, "stop")
|
|
||||||
|
|
||||||
# 保存配置到 config-example.json 文件
|
|
||||||
# with open("config-example.json", "w") as f:
|
|
||||||
# data = asdict(self)
|
|
||||||
# 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:
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
SUPPORT_MUSIC_TYPE = [
|
|
||||||
".mp3",
|
|
||||||
".flac",
|
|
||||||
".wav",
|
|
||||||
".ape",
|
|
||||||
".ogg",
|
|
||||||
]
|
|
||||||
|
|
||||||
LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}×tamp={timestamp}&limit=2"
|
|
||||||
COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={user_id}"
|
|
||||||
@@ -2,13 +2,16 @@
|
|||||||
import os
|
import os
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from flask import Flask, request, send_file, send_from_directory
|
from flask import Flask, request, send_from_directory
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
from xiaomusic import (
|
from xiaomusic import (
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
|
from xiaomusic.config import (
|
||||||
|
KEY_WORD_DICT,
|
||||||
|
)
|
||||||
from xiaomusic.utils import (
|
from xiaomusic.utils import (
|
||||||
downloadfile,
|
downloadfile,
|
||||||
)
|
)
|
||||||
@@ -38,7 +41,7 @@ def verify_password(username, password):
|
|||||||
@app.route("/allcmds")
|
@app.route("/allcmds")
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def allcmds():
|
def allcmds():
|
||||||
return xiaomusic.config.key_word_dict
|
return KEY_WORD_DICT
|
||||||
|
|
||||||
|
|
||||||
@app.route("/getversion", methods=["GET"])
|
@app.route("/getversion", methods=["GET"])
|
||||||
@@ -150,43 +153,13 @@ def delmusic():
|
|||||||
def downloadjson():
|
def downloadjson():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
log.info(data)
|
log.info(data)
|
||||||
url = data["url"]
|
ret, content = downloadfile(data["url"])
|
||||||
try:
|
|
||||||
ret = "OK"
|
|
||||||
content = downloadfile(url)
|
|
||||||
except Exception as e:
|
|
||||||
log.warning(f"downloadjson failed. url:{url} e:{e}")
|
|
||||||
ret = "Download JSON file failed."
|
|
||||||
return {
|
return {
|
||||||
"ret": ret,
|
"ret": ret,
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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,13 +82,6 @@ $(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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
<!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,11 +8,7 @@
|
|||||||
<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>小爱音箱操控面板
|
<h2>小爱音箱操控面板<span id="version">(版本未知)</span></h2>
|
||||||
(<a id="version" href="https://github.com/hanxi/xiaomusic/releases">
|
|
||||||
版本未知
|
|
||||||
</a>)
|
|
||||||
</h2>
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id="cmds">
|
<div id="cmds">
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +29,7 @@
|
|||||||
<input id="music-filename" type="text" placeholder="请输入保存为的文件名称(如:周杰伦七里香)"></input>
|
<input id="music-filename" type="text" placeholder="请输入保存为的文件名称(如:周杰伦七里香)"></input>
|
||||||
</div>
|
</div>
|
||||||
<button id="play">播放</button>
|
<button id="play">播放</button>
|
||||||
<div class="container">
|
<div class="cawait get_web_music_duration(url)ontainer">
|
||||||
<div id="playering-music" class="text"></div>
|
<div id="playering-music" class="text"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,12 +43,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<title>M3U to JSON Converter</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
|
||||||
<!--
|
|
||||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
|
||||||
<script>
|
|
||||||
// VConsole 默认会挂载到 `window.VConsole` 上
|
|
||||||
var vConsole = new window.VConsole();
|
|
||||||
</script>
|
|
||||||
-->
|
|
||||||
<script>
|
|
||||||
function handleFileSelect(evt) {
|
|
||||||
var file = evt.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
document.getElementById('m3u-input').value = e.target.result;
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
} else {
|
|
||||||
alert('无法加载文件');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertToJSON() {
|
|
||||||
var m3uContent = document.getElementById('m3u-input').value;
|
|
||||||
var lines = m3uContent.split('\n');
|
|
||||||
console.log(lines);
|
|
||||||
var musicsArray = [];
|
|
||||||
var currentName = '';
|
|
||||||
lines.forEach(function(line) {
|
|
||||||
line = line.trim();
|
|
||||||
if (line.startsWith('#EXTINF:')) {
|
|
||||||
currentName = line.replace(/.*,/g, '');
|
|
||||||
} else if (line.startsWith('http') && currentName !== '') {
|
|
||||||
musicsArray.push({"name": currentName, "type": "radio", "url": line});
|
|
||||||
currentName = ''; // Reset the name for the next entry
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var output = [{
|
|
||||||
"name": "m3u电台",
|
|
||||||
"musics": musicsArray
|
|
||||||
}];
|
|
||||||
|
|
||||||
document.getElementById('json-output').value = JSON.stringify(output, null, 2);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>M3U to JSON Converter</h1>
|
|
||||||
<input type="file" id="file-input" accept=".m3u" onchange="handleFileSelect(event)"/><br>
|
|
||||||
<textarea id="m3u-input" rows="10" cols="50" placeholder="粘贴m3u内容或上传文件..."></textarea><br>
|
|
||||||
<button onclick="convertToJSON()">转换</button><br>
|
|
||||||
<textarea id="json-output" rows="10" cols="50" placeholder="转换后的JSON..."></textarea>
|
|
||||||
</body>
|
|
||||||
<footer>
|
|
||||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
|
||||||
</footer>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@@ -8,17 +8,13 @@
|
|||||||
<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>小爱音箱设置面板
|
<h2>小爱音箱设置面板<span id="version">(版本未知)</span></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>
|
||||||
<select id="mi_did"></select>
|
<select id="mi_did"></select>
|
||||||
<label for="mi_hardware">MI_HARDWARE(型号):</label>
|
<label for="mi_hardware">MI_HARDWARE:</label>
|
||||||
<select id="mi_hardware" disabled></select>
|
<select id="mi_hardware"></select>
|
||||||
<label for="xiaomusic_search">XIAOMUSIC_SEARCH:</label>
|
<label for="xiaomusic_search">XIAOMUSIC_SEARCH:</label>
|
||||||
<select id="xiaomusic_search">
|
<select id="xiaomusic_search">
|
||||||
<option value="ytsearch:">ytsearch:</option>
|
<option value="ytsearch:">ytsearch:</option>
|
||||||
@@ -35,12 +31,6 @@
|
|||||||
<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>
|
|
||||||
|
|
||||||
<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,52 +2,36 @@ $(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 select = $(selectId);
|
|
||||||
select.empty();
|
|
||||||
optionsList.forEach(option => {
|
|
||||||
select.append(new Option(option, option));
|
|
||||||
});
|
|
||||||
select.val(selectedOption);
|
|
||||||
};
|
|
||||||
|
|
||||||
let isChanging = false;
|
|
||||||
// 更新下拉菜单的函数
|
|
||||||
const updateSelect = (selectId, value) => {
|
|
||||||
if (!isChanging) {
|
|
||||||
isChanging = true;
|
|
||||||
$(selectId).val(value);
|
|
||||||
isChanging = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 联动逻辑
|
|
||||||
const linkSelects = (sourceSelect, sourceList, targetSelect, targetList) => {
|
|
||||||
$(sourceSelect).change(function() {
|
|
||||||
if (!isChanging) {
|
|
||||||
const selectedValue = $(this).val();
|
|
||||||
const selectedIndex = sourceList.indexOf(selectedValue);
|
|
||||||
console.log(selectedIndex, selectedValue,sourceList,targetList)
|
|
||||||
if (selectedIndex !== -1) {
|
|
||||||
updateSelect(targetSelect, targetList[selectedIndex]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// 拉取现有配置
|
// 拉取现有配置
|
||||||
$.get("/getsetting", function(data, status) {
|
$.get("/getsetting", function(data, status) {
|
||||||
console.log(data, status);
|
console.log(data, status);
|
||||||
|
|
||||||
updateSelectOptions("#mi_did", data.mi_did_list, data.mi_did);
|
var mi_did_div = $("#mi_did")
|
||||||
updateSelectOptions("#mi_hardware", data.mi_hardware_list, data.mi_hardware);
|
mi_did_div.empty();
|
||||||
|
$.each(data.mi_did_list, function(index, option){
|
||||||
|
mi_did_div.append($('<option>', {
|
||||||
|
value:option,
|
||||||
|
text:option,
|
||||||
|
}));
|
||||||
|
if (data.mi_did == option) {
|
||||||
|
mi_did_div.val(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 初始化联动
|
var mi_hardware_div = $("#mi_hardware")
|
||||||
linkSelects('#mi_did', data.mi_did_list, '#mi_hardware', data.mi_hardware_list);
|
mi_hardware_div.empty();
|
||||||
|
$.each(data.mi_hardware_list, function(index, option){
|
||||||
|
mi_hardware_div.append($('<option>', {
|
||||||
|
value:option,
|
||||||
|
text:option,
|
||||||
|
}));
|
||||||
|
if (data.mi_hardware == option) {
|
||||||
|
mi_hardware_div.val(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (data.xiaomusic_search != "") {
|
if (data.xiaomusic_search != "") {
|
||||||
$("#xiaomusic_search").val(data.xiaomusic_search);
|
$("#xiaomusic_search").val(data.xiaomusic_search);
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
.button {
|
button {
|
||||||
line-height: 50px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
button, .button {
|
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -14,7 +10,7 @@ button, .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);
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import difflib
|
import difflib
|
||||||
import os
|
import os
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import string
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
@@ -16,8 +14,6 @@ import mutagen
|
|||||||
import requests
|
import requests
|
||||||
from requests.utils import cookiejar_from_dict
|
from requests.utils import cookiejar_from_dict
|
||||||
|
|
||||||
from xiaomusic.const import SUPPORT_MUSIC_TYPE
|
|
||||||
|
|
||||||
|
|
||||||
### HELP FUNCTION ###
|
### HELP FUNCTION ###
|
||||||
def parse_cookie_string(cookie_string):
|
def parse_cookie_string(cookie_string):
|
||||||
@@ -72,12 +68,7 @@ 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, n=10, cutoff=0.1)
|
return difflib.get_close_matches(user_input, collection, 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
|
|
||||||
|
|
||||||
|
|
||||||
# 歌曲排序
|
# 歌曲排序
|
||||||
@@ -160,23 +151,19 @@ def walk_to_depth(root, depth=None, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def downloadfile(url):
|
def downloadfile(url):
|
||||||
# 清理和验证URL
|
try:
|
||||||
# 解析URL
|
response = requests.get(url, timeout=5) # 增加超时以避免长时间挂起
|
||||||
parsed_url = urlparse(url)
|
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
|
||||||
|
return ("OK", response.text)
|
||||||
# 基础验证:仅允许HTTP和HTTPS协议
|
except requests.exceptions.HTTPError as errh:
|
||||||
if parsed_url.scheme not in ("http", "https"):
|
return (f"HTTP Error: {errh}", "")
|
||||||
raise Warning(
|
except requests.exceptions.ConnectionError as errc:
|
||||||
f"Invalid URL scheme: {parsed_url.scheme}. Only HTTP and HTTPS are allowed."
|
return (f"Error Connecting: {errc}", "")
|
||||||
)
|
except requests.exceptions.Timeout as errt:
|
||||||
|
return (f"Timeout Error: {errt}", "")
|
||||||
# 构建目标URL
|
except requests.exceptions.RequestException as err:
|
||||||
cleaned_url = parsed_url.geturl()
|
return (f"Oops: Something Else, {err}", "")
|
||||||
|
return ("Unknow Error", "")
|
||||||
# 发起请求
|
|
||||||
response = requests.get(cleaned_url, timeout=5) # 增加超时以避免长时间挂起
|
|
||||||
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
|
|
||||||
return response.text
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_web_music_duration(session, url, start=0, end=500):
|
async def _get_web_music_duration(session, url, start=0, end=500):
|
||||||
@@ -200,20 +187,6 @@ async def _get_web_music_duration(session, url, start=0, end=500):
|
|||||||
async def get_web_music_duration(url, start=0, end=500):
|
async def get_web_music_duration(url, start=0, end=500):
|
||||||
duration = 0
|
duration = 0
|
||||||
try:
|
try:
|
||||||
parsed_url = urlparse(url)
|
|
||||||
file_path = parsed_url.path
|
|
||||||
_, extension = os.path.splitext(file_path)
|
|
||||||
if extension.lower() not in SUPPORT_MUSIC_TYPE:
|
|
||||||
cleaned_url = parsed_url.geturl()
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(
|
|
||||||
cleaned_url,
|
|
||||||
allow_redirects=True,
|
|
||||||
headers={
|
|
||||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
|
|
||||||
},
|
|
||||||
) as response:
|
|
||||||
url = str(response.url)
|
|
||||||
# 设置总超时时间为3秒
|
# 设置总超时时间为3秒
|
||||||
timeout = aiohttp.ClientTimeout(total=3)
|
timeout = aiohttp.ClientTimeout(total=3)
|
||||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||||
@@ -224,7 +197,7 @@ async def get_web_music_duration(url, start=0, end=500):
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return duration, url
|
return duration
|
||||||
|
|
||||||
|
|
||||||
# 获取文件播放时长
|
# 获取文件播放时长
|
||||||
@@ -236,7 +209,3 @@ def get_local_music_duration(filename):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return duration
|
return duration
|
||||||
|
|
||||||
|
|
||||||
def get_random(length):
|
|
||||||
return "".join(random.sample(string.ascii_letters + string.digits, length))
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/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
|
||||||
@@ -10,7 +9,6 @@ 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
|
||||||
@@ -20,18 +18,17 @@ from xiaomusic import (
|
|||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
from xiaomusic.config import (
|
from xiaomusic.config import (
|
||||||
KEY_WORD_ARG_BEFORE_DICT,
|
|
||||||
Config,
|
|
||||||
)
|
|
||||||
from xiaomusic.const import (
|
|
||||||
COOKIE_TEMPLATE,
|
COOKIE_TEMPLATE,
|
||||||
|
KEY_MATCH_ORDER,
|
||||||
|
KEY_WORD_ARG_BEFORE_DICT,
|
||||||
|
KEY_WORD_DICT,
|
||||||
LATEST_ASK_API,
|
LATEST_ASK_API,
|
||||||
SUPPORT_MUSIC_TYPE,
|
SUPPORT_MUSIC_TYPE,
|
||||||
|
Config,
|
||||||
)
|
)
|
||||||
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_web_music_duration,
|
get_web_music_duration,
|
||||||
@@ -93,8 +90,14 @@ class XiaoMusic:
|
|||||||
# 关机定时器
|
# 关机定时器
|
||||||
self._stop_timer = None
|
self._stop_timer = None
|
||||||
|
|
||||||
# 初始化日志
|
# setup logger
|
||||||
self.setup_logger()
|
logging.basicConfig(
|
||||||
|
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()
|
||||||
@@ -105,30 +108,7 @@ class XiaoMusic:
|
|||||||
# 启动时初始化获取声音
|
# 启动时初始化获取声音
|
||||||
self.set_last_record("get_volume#")
|
self.set_last_record("get_volume#")
|
||||||
|
|
||||||
def setup_logger(self):
|
self.log.info("ffmpeg_location: %s", self.ffmpeg_location)
|
||||||
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:
|
||||||
@@ -289,13 +269,10 @@ 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):
|
||||||
@@ -307,25 +284,8 @@ 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):
|
||||||
ret = await self.mina_service.player_pause(self.device_id)
|
await self.mina_service.player_stop(self.device_id)
|
||||||
self.log.debug(f"force_stop_xiaoai player_pause ret:{ret}")
|
|
||||||
|
|
||||||
# 是否在下载中
|
# 是否在下载中
|
||||||
def is_downloading(self):
|
def is_downloading(self):
|
||||||
@@ -400,28 +360,7 @@ class XiaoMusic:
|
|||||||
url = self._all_music[name]
|
url = self._all_music[name]
|
||||||
return url.startswith(("http://", "https://"))
|
return url.startswith(("http://", "https://"))
|
||||||
|
|
||||||
# 获取歌曲播放时长,播放地址
|
# 获取歌曲播放地址
|
||||||
async def get_music_sec_url(self, name):
|
|
||||||
sec = 0
|
|
||||||
url = self.get_music_url(name)
|
|
||||||
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)
|
|
||||||
sec = int(duration)
|
|
||||||
self.log.info(f"网络歌曲 {name} : {origin_url} {url} 的时长 {sec} 秒")
|
|
||||||
else:
|
|
||||||
filename = self.get_filename(name)
|
|
||||||
sec = int(get_local_music_duration(filename))
|
|
||||||
self.log.info(f"本地歌曲 {name} : {filename} {url} 的时长 {sec} 秒")
|
|
||||||
|
|
||||||
if sec <= 0:
|
|
||||||
self.log.warning(f"获取歌曲时长失败 {name} {url}")
|
|
||||||
return sec, url
|
|
||||||
|
|
||||||
def get_music_url(self, name):
|
def get_music_url(self, name):
|
||||||
if self.is_web_music(name):
|
if self.is_web_music(name):
|
||||||
url = self._all_music[name]
|
url = self._all_music[name]
|
||||||
@@ -489,7 +428,6 @@ 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:
|
||||||
@@ -510,7 +448,7 @@ class XiaoMusic:
|
|||||||
# 处理电台列表
|
# 处理电台列表
|
||||||
if music_type == "radio":
|
if music_type == "radio":
|
||||||
self._all_radio[name] = url
|
self._all_radio[name] = url
|
||||||
self.log.debug(one_music_list)
|
self.log.info(one_music_list)
|
||||||
# 歌曲名字相同会覆盖
|
# 歌曲名字相同会覆盖
|
||||||
self._music_list[list_name] = one_music_list
|
self._music_list[list_name] = one_music_list
|
||||||
if self._all_radio:
|
if self._all_radio:
|
||||||
@@ -559,14 +497,30 @@ class XiaoMusic:
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
# 设置下一首歌曲的播放定时器
|
# 设置下一首歌曲的播放定时器
|
||||||
async def set_next_music_timeout(self, sec):
|
async def set_next_music_timeout(self):
|
||||||
if sec <= 0:
|
name = self.cur_music
|
||||||
|
if self.is_web_radio_music(name):
|
||||||
|
self.log.info("电台不会有下一首的定时器")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.is_web_music(name):
|
||||||
|
url = self._all_music[name]
|
||||||
|
duration = await get_web_music_duration(url)
|
||||||
|
sec = int(duration)
|
||||||
|
self.log.info(f"网络歌曲 {name} : {url} 的时长 {sec} 秒")
|
||||||
|
else:
|
||||||
|
filename = self.get_filename(name)
|
||||||
|
sec = int(get_local_music_duration(filename))
|
||||||
|
self.log.info(f"本地歌曲 {name} : {filename} 的时长 {sec} 秒")
|
||||||
|
|
||||||
if self._next_timer:
|
if self._next_timer:
|
||||||
self._next_timer.cancel()
|
self._next_timer.cancel()
|
||||||
self.log.info("定时器已取消")
|
self.log.info("定时器已取消")
|
||||||
|
|
||||||
|
if sec <= 0:
|
||||||
|
self.log.warning("获取歌曲时长失败,不会开启下一首歌曲的定时器")
|
||||||
|
return
|
||||||
|
|
||||||
self._timeout = sec
|
self._timeout = sec
|
||||||
|
|
||||||
async def _do_next():
|
async def _do_next():
|
||||||
@@ -577,7 +531,7 @@ class XiaoMusic:
|
|||||||
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
||||||
|
|
||||||
self._next_timer = asyncio.ensure_future(_do_next())
|
self._next_timer = asyncio.ensure_future(_do_next())
|
||||||
self.log.info(f"{sec}秒后将会播放下一首歌曲")
|
self.log.info(f"{sec}秒后将会播放下一首")
|
||||||
|
|
||||||
async def run_forever(self):
|
async def run_forever(self):
|
||||||
StartHTTPServer(self.port, self.music_path, self)
|
StartHTTPServer(self.port, self.music_path, self)
|
||||||
@@ -587,11 +541,10 @@ class XiaoMusic:
|
|||||||
task = asyncio.create_task(self.poll_latest_ask())
|
task = asyncio.create_task(self.poll_latest_ask())
|
||||||
assert task is not None # to keep the reference to task, do not remove this
|
assert task is not None # to keep the reference to task, do not remove this
|
||||||
filtered_keywords = [
|
filtered_keywords = [
|
||||||
keyword for keyword in self.config.key_match_order if "#" not in keyword
|
keyword for keyword in KEY_MATCH_ORDER if "#" not in keyword
|
||||||
]
|
]
|
||||||
joined_keywords = "/".join(filtered_keywords)
|
joined_keywords = "/".join(filtered_keywords)
|
||||||
self.log.info(f"Running xiaomusic now, 用`{joined_keywords}`开头来控制")
|
self.log.info(f"Running xiaomusic now, 用`{joined_keywords}`开头来控制")
|
||||||
self.log.info(self.config.key_word_dict)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self.polling_event.set()
|
self.polling_event.set()
|
||||||
@@ -610,7 +563,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.info("收到消息:%s 控制面板:%s", query, ctrl_panel)
|
self.log.debug("收到消息:%s 控制面板:%s", query, ctrl_panel)
|
||||||
|
|
||||||
# 匹配命令
|
# 匹配命令
|
||||||
opvalue, oparg = self.match_cmd(query, ctrl_panel)
|
opvalue, oparg = self.match_cmd(query, ctrl_panel)
|
||||||
@@ -624,27 +577,9 @@ 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):
|
||||||
# 优先处理完全匹配
|
for opkey in KEY_MATCH_ORDER:
|
||||||
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:
|
|
||||||
patternarg = rf"(.*){opkey}(.*)"
|
patternarg = rf"(.*){opkey}(.*)"
|
||||||
# 匹配参数
|
# 匹配参数
|
||||||
matcharg = re.match(patternarg, query)
|
matcharg = re.match(patternarg, query)
|
||||||
@@ -661,18 +596,20 @@ class XiaoMusic:
|
|||||||
argafter,
|
argafter,
|
||||||
)
|
)
|
||||||
oparg = argafter
|
oparg = argafter
|
||||||
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
opvalue = KEY_WORD_DICT[opkey]
|
||||||
oparg = argpre
|
|
||||||
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.ifno(f"不在激活命令中 {opvalue}")
|
self.log.debug(f"不在激活命令中 {opvalue}")
|
||||||
continue
|
continue
|
||||||
self.log.info(f"匹配到指令. opkey:{opkey} opvalue:{opvalue} oparg:{oparg}")
|
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
||||||
|
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", "notts")
|
return ("stop", {})
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
# 判断是否播放下一首歌曲
|
# 判断是否播放下一首歌曲
|
||||||
@@ -686,66 +623,6 @@ class XiaoMusic:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def play_url(self, **kwargs):
|
|
||||||
url = kwargs.get("arg1", "")
|
|
||||||
if self.config.use_music_api:
|
|
||||||
ret = await self.mina_service.play_by_music_url(self.device_id, url, _type=2)
|
|
||||||
self.log.debug(
|
|
||||||
f"play_url play_by_music_url {self.config.hardware}. ret:{ret} url:{url}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ret = await self.mina_service.play_by_url(self.device_id, url)
|
|
||||||
self.log.debug(
|
|
||||||
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):
|
|
||||||
name = kwargs.get("arg1", "")
|
|
||||||
if name == "":
|
|
||||||
if self.check_play_next():
|
|
||||||
await self.play_next()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
name = self.cur_music
|
|
||||||
|
|
||||||
self.log.info(f"playlocal. name:{name}")
|
|
||||||
|
|
||||||
# 本地歌曲不存在时下载
|
|
||||||
name = self.find_real_music_name(name)
|
|
||||||
if not self.is_music_exist(name):
|
|
||||||
await self.do_tts(f"本地不存在歌曲{name}")
|
|
||||||
return
|
|
||||||
await self._playmusic(name)
|
|
||||||
|
|
||||||
async def _playmusic(self, name):
|
|
||||||
self._playing = True
|
|
||||||
self.cur_music = name
|
|
||||||
self.log.info(f"cur_music {self.cur_music}")
|
|
||||||
sec, url = await self.get_music_sec_url(name)
|
|
||||||
self.log.info(f"播放 {url}")
|
|
||||||
await self.force_stop_xiaoai()
|
|
||||||
await self.play_url(arg1=url)
|
|
||||||
self.log.info("已经开始播放了")
|
|
||||||
# 设置下一首歌曲的播放定时器
|
|
||||||
await self.set_next_music_timeout(sec)
|
|
||||||
|
|
||||||
# 播放歌曲
|
# 播放歌曲
|
||||||
async def play(self, **kwargs):
|
async def play(self, **kwargs):
|
||||||
parts = kwargs.get("arg1", "").split("|")
|
parts = kwargs.get("arg1", "").split("|")
|
||||||
@@ -764,17 +641,23 @@ 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:
|
|
||||||
await self.do_tts(f"本地不存在歌曲{name}")
|
|
||||||
return
|
|
||||||
await self.download(search_key, name)
|
await self.download(search_key, name)
|
||||||
self.log.info(f"正在下载中 {search_key} {name}")
|
self.log.info("正在下载中 %s", search_key + ":" + name)
|
||||||
await self.download_proc.wait()
|
await self.download_proc.wait()
|
||||||
# 把文件插入到播放列表里
|
# 把文件插入到播放列表里
|
||||||
self.add_download_music(name)
|
self.add_download_music(name)
|
||||||
await self._playmusic(name)
|
|
||||||
|
self._playing = True
|
||||||
|
self.cur_music = name
|
||||||
|
self.log.info("cur_music %s", self.cur_music)
|
||||||
|
url = self.get_music_url(name)
|
||||||
|
self.log.info("播放 %s", url)
|
||||||
|
await self.force_stop_xiaoai()
|
||||||
|
await self.mina_service.play_by_url(self.device_id, url)
|
||||||
|
self.log.info("已经开始播放了")
|
||||||
|
# 设置下一首歌曲的播放定时器
|
||||||
|
await self.set_next_music_timeout()
|
||||||
|
|
||||||
# 下一首
|
# 下一首
|
||||||
async def play_next(self, **kwargs):
|
async def play_next(self, **kwargs):
|
||||||
@@ -826,27 +709,10 @@ 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
|
||||||
@@ -864,9 +730,6 @@ 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("定时器已取消")
|
||||||
@@ -881,12 +744,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(arg1="notts")
|
await self.stop()
|
||||||
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())
|
||||||
await self.do_tts(f"收到,{minute}分钟后将关机")
|
self.log.info(f"{minute}分钟后将关机")
|
||||||
|
|
||||||
async def set_volume(self, **kwargs):
|
async def set_volume(self, **kwargs):
|
||||||
value = kwargs.get("arg1", 0)
|
value = kwargs.get("arg1", 0)
|
||||||
@@ -896,7 +759,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", 0
|
"volume", 5
|
||||||
)
|
)
|
||||||
self.log.info("get_volume. volume:%s", self._volume)
|
self.log.info("get_volume. volume:%s", self._volume)
|
||||||
|
|
||||||
@@ -970,7 +833,7 @@ class XiaoMusic:
|
|||||||
|
|
||||||
self.search_prefix = self.config.search_prefix
|
self.search_prefix = self.config.search_prefix
|
||||||
self.proxy = self.config.proxy
|
self.proxy = self.config.proxy
|
||||||
self.log.debug("update_config_from_setting ok. data:%s", data)
|
self.log.info("update_config_from_setting ok. data:%s", data)
|
||||||
|
|
||||||
# 重新初始化
|
# 重新初始化
|
||||||
async def reinit(self, **kwargs):
|
async def reinit(self, **kwargs):
|
||||||
@@ -1011,15 +874,3 @@ 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