mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-06 14:52:50 +08:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f01665c998 | ||
|
|
ac23080f6a | ||
|
|
15ee6c4dd1 | ||
|
|
aeaa8f8925 | ||
|
|
59d7e056c4 | ||
|
|
e79afa46b3 | ||
|
|
9714f3d064 | ||
|
|
2c35c6cfd6 | ||
|
|
88f0ce7e51 | ||
|
|
e484164fad | ||
|
|
aa6bce75cd | ||
|
|
512efb595a | ||
|
|
e5dea8e693 | ||
|
|
a704f8003c | ||
|
|
349a25ad58 | ||
|
|
746f46edb3 | ||
|
|
4a29c7a124 | ||
|
|
0e1e412ee9 | ||
|
|
2e84f7c830 | ||
|
|
61a0d68b6a | ||
|
|
ae90029d8e | ||
|
|
ccc83a518c | ||
|
|
7884a5769f | ||
|
|
a663bb330e | ||
|
|
346f0af543 | ||
|
|
49ec1bb7c0 | ||
|
|
fc0cc75dea | ||
|
|
6fc2be5d31 | ||
|
|
db680bf1ba | ||
|
|
1c2b97c0d2 | ||
|
|
59cfbb06a4 | ||
|
|
9291676543 | ||
|
|
0d2ba60728 | ||
|
|
cd1461df6d | ||
|
|
37abfd9ce2 | ||
|
|
c6de3dfd00 | ||
|
|
ae297c780a | ||
|
|
e07a06c8e4 | ||
|
|
1c91f39417 | ||
|
|
13d26be0a2 | ||
|
|
c2740533a8 | ||
|
|
abbc2f25bb | ||
|
|
04b9738a77 | ||
|
|
f5932a301e | ||
|
|
f11e194e6a | ||
|
|
259e47d4d3 | ||
|
|
96c3e00902 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -25,6 +25,6 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -61,6 +61,6 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:latest, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:stable
|
||||
|
||||
52
README.md
52
README.md
@@ -2,7 +2,28 @@
|
||||
|
||||
使用小爱/红米音箱播放音乐,音乐使用 yt-dlp 下载。
|
||||
|
||||
## 运行
|
||||
## 最简配置运行
|
||||
|
||||
已经支持在 web 页面配置其他参数,docker compose 配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
xiaomusic:
|
||||
image: hanxi/xiaomusic
|
||||
container_name: xiaomusic
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
environment:
|
||||
MI_USER: '小米账号'
|
||||
MI_PASS: '小米密码'
|
||||
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
||||
```
|
||||
启动成功后,在 web 页面可以配置 MI_DID, MI_HARDWARE, XIAOMUSIC_SEARCH, XIAOMUSIC_PROXY 参数。
|
||||
|
||||
## 开发环境运行
|
||||
|
||||
- 使用 install_dependencies.sh 下载依赖
|
||||
- 使用 pdm 安装环境
|
||||
@@ -28,6 +49,11 @@ pdm run xiaomusic.py
|
||||
- 下一首
|
||||
- 单曲循环
|
||||
- 全部循环
|
||||
- 随机播放
|
||||
- 关机
|
||||
- 停止播放
|
||||
- 刷新列表
|
||||
- 播放列表+列表名 比如:播放列表其他
|
||||
|
||||
> 隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会播放小猪佩奇的故事。
|
||||
|
||||
@@ -50,8 +76,17 @@ pdm run xiaomusic.py
|
||||
## 在 Docker 里使用
|
||||
|
||||
```shell
|
||||
docker run -e MI_USER=<your-xiaomi-account> -e MI_PASS=<your-xiaomi-password> -e MI_DID=<your-xiaomi-speaker-mid> -e MI_HARDWARE='L07A' -e XIAOMUSIC_PROXY=<proxy-for-yt-dlp> -e XIAOMUSIC_HOSTNAME=192.168.2.5 -e XIAOMUSIC_SEARCH='bilisearch:' -p 8090:8090 -v ./music:/app/music hanxi/xiaomusic
|
||||
docker run -e MI_USER=<your-xiaomi-account> \
|
||||
-e MI_PASS='your-xiaomi-password' \
|
||||
-e MI_DID='your-xiaomi-speaker-mid' \
|
||||
-e MI_HARDWARE='L07A' \
|
||||
-e XIAOMUSIC_PROXY='proxy-for-yt-dlp' \
|
||||
-e XIAOMUSIC_HOSTNAME=192.168.2.5 \
|
||||
-e XIAOMUSIC_SEARCH='bilisearch:' \
|
||||
-p 8090:8090 \
|
||||
-v ./music:/app/music hanxi/xiaomusic
|
||||
```
|
||||
|
||||
- XIAOMUSIC_SEARCH 可以配置为 'bilisearch:' 表示歌曲从哔哩哔哩下载;
|
||||
- 配置为 'ytsearch:' 表示歌曲从 youtube 下载。
|
||||
- XIAOMUSIC_PROXY 用于配置代理,默认为空;
|
||||
@@ -137,6 +172,19 @@ services:
|
||||
- 新功能
|
||||
- 显示正在播放的歌曲
|
||||
- 模糊搜索本地歌曲
|
||||
- 设置页面
|
||||
|
||||
|
||||
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
|
||||
- MI_USER
|
||||
- MI_PASS
|
||||
- XIAOMUSIC_HOSTNAME
|
||||
|
||||
其他的这些可以在网页里配置:
|
||||
- MI_DID
|
||||
- MI_HARDWARE
|
||||
- XIAOMUSIC_SEARCH
|
||||
- XIAOMUSIC_PROXY
|
||||
|
||||
## 讨论区
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
set -e
|
||||
|
||||
version_file=./pyproject.toml
|
||||
init_file=./xiaomusic/__init__.py
|
||||
# 获取当前版本号
|
||||
current_version=$(grep -oE "version = \"[0-9]+\.[0-9]+\.[0-9]+\"" $version_file | cut -d'"' -f2)
|
||||
echo "当前版本号: "$current_version
|
||||
@@ -24,11 +25,13 @@ new_version="$major.$minor.$patch"
|
||||
|
||||
# 将新版本号写入文件
|
||||
sed -i "s/version.*/version = \"$new_version\"/g" $version_file
|
||||
sed -i "s/__version__.*/__version__ = \"$new_version\"/g" $init_file
|
||||
|
||||
echo "新版本号:$new_version"
|
||||
|
||||
git diff
|
||||
git add $version_file
|
||||
git add $init_file
|
||||
git commit -m "new version v$new_version"
|
||||
git tag v$new_version
|
||||
git push -u origin main --tags
|
||||
|
||||
53
pdm.lock
generated
53
pdm.lock
generated
@@ -2,10 +2,10 @@
|
||||
# It is not intended for manual editing.
|
||||
|
||||
[metadata]
|
||||
groups = ["default"]
|
||||
groups = ["default", "lint"]
|
||||
strategy = ["cross_platform"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:3ebe2bfca553a4141b8da2df035ed818068d32bdabe083a4e2bc0396dd14b81c"
|
||||
content_hash = "sha256:fa83f8134ccb4a432304dead5fe1303899418558634abaa0824e8d9fdfb1f490"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@@ -507,8 +507,8 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "miservice-fork"
|
||||
version = "2.4.3"
|
||||
requires_python = ">=3.7"
|
||||
version = "2.5.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "XiaoMi Cloud Service fork from https://github.com/Yonsm/MiService"
|
||||
dependencies = [
|
||||
"aiohttp",
|
||||
@@ -516,8 +516,8 @@ dependencies = [
|
||||
"rich",
|
||||
]
|
||||
files = [
|
||||
{file = "miservice_fork-2.4.3-py3-none-any.whl", hash = "sha256:e93732dbfff11435dede56ebf2c76aca6284d5d54dc198d260a684c9ea16fc07"},
|
||||
{file = "miservice_fork-2.4.3.tar.gz", hash = "sha256:a0f7cbba41d48904d2914ea707934ebc9ddb107ae528cfb990a8da8fa9225105"},
|
||||
{file = "miservice_fork-2.5.0-py3-none-any.whl", hash = "sha256:97b6360ea53c34fe035ac9d94e8705f305b8fa7fc2b44a7aea182449a76cb622"},
|
||||
{file = "miservice_fork-2.5.0.tar.gz", hash = "sha256:8ca2d370d5b32f7e330add38aa1912d734aefa7880f16cef9eac110a5a3029e2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -648,6 +648,31 @@ files = [
|
||||
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.4.8"
|
||||
requires_python = ">=3.7"
|
||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
files = [
|
||||
{file = "ruff-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066"},
|
||||
{file = "ruff-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b"},
|
||||
{file = "ruff-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45"},
|
||||
{file = "ruff-0.4.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a"},
|
||||
{file = "ruff-0.4.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc"},
|
||||
{file = "ruff-0.4.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed"},
|
||||
{file = "ruff-0.4.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa"},
|
||||
{file = "ruff-0.4.8-py3-none-win32.whl", hash = "sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9"},
|
||||
{file = "ruff-0.4.8-py3-none-win_amd64.whl", hash = "sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d"},
|
||||
{file = "ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780"},
|
||||
{file = "ruff-0.4.8.tar.gz", hash = "sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
@@ -668,6 +693,16 @@ files = [
|
||||
{file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waitress"
|
||||
version = "3.0.0"
|
||||
requires_python = ">=3.8.0"
|
||||
summary = "Waitress WSGI server"
|
||||
files = [
|
||||
{file = "waitress-3.0.0-py3-none-any.whl", hash = "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669"},
|
||||
{file = "waitress-3.0.0.tar.gz", hash = "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "12.0"
|
||||
@@ -784,7 +819,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "yt-dlp"
|
||||
version = "2024.5.11.232654.dev0"
|
||||
version = "2024.5.16.232713.dev0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A feature-rich command-line audio/video downloader"
|
||||
dependencies = [
|
||||
@@ -798,6 +833,6 @@ dependencies = [
|
||||
"websockets>=12.0",
|
||||
]
|
||||
files = [
|
||||
{file = "yt_dlp-2024.5.11.232654.dev0-py3-none-any.whl", hash = "sha256:a17f49d6ddabd398d4b9d39d0dc2903aa49029542139f50cb613022c606927b0"},
|
||||
{file = "yt_dlp-2024.5.11.232654.dev0.tar.gz", hash = "sha256:d1a3c95aff5fa512769f4825e4b7992ace6ffc72fae2a318e5af05e3bf43767d"},
|
||||
{file = "yt_dlp-2024.5.16.232713.dev0-py3-none-any.whl", hash = "sha256:42d3c27ab77583ff67ee2ddc94e376ea2a76a561ed8b1836ee04fd1cd23ad88c"},
|
||||
{file = "yt_dlp-2024.5.16.232713.dev0.tar.gz", hash = "sha256:d431187fa703c9f52225080ae56471272679e44d9363f97b7b3187d37a5e6480"},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "xiaomusic"
|
||||
version = "0.1.27"
|
||||
version = "0.1.45"
|
||||
description = "Play Music with xiaomi AI speaker"
|
||||
authors = [
|
||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||
@@ -9,10 +9,11 @@ dependencies = [
|
||||
"rich>=13.6.0",
|
||||
"requests>=2.31.0",
|
||||
"aiohttp>=3.8.6",
|
||||
"miservice-fork>=2.4.4",
|
||||
"miservice-fork>=2.5.0",
|
||||
"mutagen>=1.47.0",
|
||||
"yt-dlp>=2024.04.09",
|
||||
"flask[async]>=3.0.1",
|
||||
"waitress>=3.0.0",
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
readme = "README.md"
|
||||
@@ -21,3 +22,31 @@ license = {text = "MIT"}
|
||||
[build-system]
|
||||
requires = ["pdm-backend"]
|
||||
build-backend = "pdm.backend"
|
||||
|
||||
[tool.pdm]
|
||||
[tool.pdm.dev-dependencies]
|
||||
lint = [
|
||||
"ruff>=0.4.8",
|
||||
]
|
||||
[tool.ruff]
|
||||
select = [
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"E", # pycodestyle - Error
|
||||
"F", # Pyflakes
|
||||
"I", # isort
|
||||
"W", # pycodestyle - Warning
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
ignore = [
|
||||
"E501", # line-too-long
|
||||
"W191", # tab-indentation
|
||||
]
|
||||
include = ["**/*.py", "**/*.pyi", "**/pyproject.toml"]
|
||||
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[tool.pdm.scripts]
|
||||
lint = "ruff check ."
|
||||
fmt = "ruff format ."
|
||||
|
||||
@@ -302,9 +302,9 @@ MarkupSafe==2.1.4 \
|
||||
mdurl==0.1.2 \
|
||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||
miservice-fork==2.4.3 \
|
||||
--hash=sha256:a0f7cbba41d48904d2914ea707934ebc9ddb107ae528cfb990a8da8fa9225105 \
|
||||
--hash=sha256:e93732dbfff11435dede56ebf2c76aca6284d5d54dc198d260a684c9ea16fc07
|
||||
miservice-fork==2.5.0 \
|
||||
--hash=sha256:8ca2d370d5b32f7e330add38aa1912d734aefa7880f16cef9eac110a5a3029e2 \
|
||||
--hash=sha256:97b6360ea53c34fe035ac9d94e8705f305b8fa7fc2b44a7aea182449a76cb622
|
||||
multidict==6.0.4 \
|
||||
--hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \
|
||||
--hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \
|
||||
@@ -380,6 +380,9 @@ typing-extensions==4.9.0; python_version < "3.11" \
|
||||
urllib3==2.0.6 \
|
||||
--hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \
|
||||
--hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564
|
||||
waitress==3.0.0 \
|
||||
--hash=sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1 \
|
||||
--hash=sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669
|
||||
websockets==12.0 \
|
||||
--hash=sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b \
|
||||
--hash=sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6 \
|
||||
@@ -466,6 +469,6 @@ yarl==1.9.2 \
|
||||
--hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \
|
||||
--hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \
|
||||
--hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560
|
||||
yt-dlp==2024.5.11.232654.dev0 \
|
||||
--hash=sha256:a17f49d6ddabd398d4b9d39d0dc2903aa49029542139f50cb613022c606927b0 \
|
||||
--hash=sha256:d1a3c95aff5fa512769f4825e4b7992ace6ffc72fae2a318e5af05e3bf43767d
|
||||
yt-dlp==2024.5.16.232713.dev0 \
|
||||
--hash=sha256:42d3c27ab77583ff67ee2ddc94e376ea2a76a561ed8b1836ee04fd1cd23ad88c \
|
||||
--hash=sha256:d431187fa703c9f52225080ae56471272679e44d9363f97b7b3187d37a5e6480
|
||||
|
||||
@@ -1 +1 @@
|
||||
pdm export -o requirements.txt
|
||||
pdm export --prod -o requirements.txt
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "0.1.45"
|
||||
|
||||
@@ -3,18 +3,17 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Iterable
|
||||
from dataclasses import dataclass
|
||||
|
||||
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"
|
||||
COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={user_id}"
|
||||
|
||||
HARDWARE_COMMAND_DICT = {
|
||||
# hardware: (tts_command, wakeup_command, volume_command)
|
||||
"LX06": ("5-1", "5-5", "2-1"),
|
||||
"L05B": ("5-3", "5-4", "2-1"),
|
||||
"S12": ("5-1", "5-5", "2-1"), # 第一代小爱,型号MDZ-25-DA
|
||||
"S12A": ("5-1", "5-5", "2-1"),
|
||||
"LX01": ("5-1", "5-5", "2-1"),
|
||||
"L06A": ("5-1", "5-5", "2-1"),
|
||||
@@ -43,7 +42,10 @@ KEY_WORD_DICT = {
|
||||
"关机": "stop",
|
||||
"停止播放": "stop",
|
||||
"分钟后关机": "stop_after_minute",
|
||||
"播放列表": "play_music_list",
|
||||
"刷新列表": "gen_music_list",
|
||||
"set_volume#": "set_volume",
|
||||
"get_volume#": "get_volume",
|
||||
}
|
||||
|
||||
# 命令参数在前面
|
||||
@@ -54,6 +56,7 @@ KEY_WORD_ARG_BEFORE_DICT = {
|
||||
# 匹配优先级
|
||||
KEY_MATCH_ORDER = [
|
||||
"set_volume#",
|
||||
"get_volume#",
|
||||
"分钟后关机",
|
||||
"播放歌曲",
|
||||
"放歌曲",
|
||||
@@ -63,6 +66,8 @@ KEY_MATCH_ORDER = [
|
||||
"随机播放",
|
||||
"关机",
|
||||
"停止播放",
|
||||
"刷新列表",
|
||||
"播放列表",
|
||||
]
|
||||
|
||||
SUPPORT_MUSIC_TYPE = [
|
||||
@@ -80,7 +85,7 @@ class Config:
|
||||
mute_xiaoai: bool = True
|
||||
cookie: str = ""
|
||||
use_command: bool = False
|
||||
verbose: bool = False
|
||||
verbose: bool = os.getenv("XIAOMUSIC_VERBOSE", "").lower() == "true"
|
||||
music_path: str = os.getenv("XIAOMUSIC_MUSIC_PATH", "music")
|
||||
hostname: str = os.getenv("XIAOMUSIC_HOSTNAME", "192.168.2.5")
|
||||
port: int = int(os.getenv("XIAOMUSIC_PORT", "8090"))
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from flask import Flask, request, send_from_directory
|
||||
from threading import Thread
|
||||
|
||||
from flask import Flask, request, send_from_directory
|
||||
from waitress import serve
|
||||
|
||||
from xiaomusic import (
|
||||
__version__,
|
||||
)
|
||||
from xiaomusic.config import (
|
||||
KEY_WORD_DICT,
|
||||
)
|
||||
|
||||
# 隐藏 flask 启动告警
|
||||
# https://gist.github.com/jerblack/735b9953ba1ab6234abb43174210d356
|
||||
# from flask import cli
|
||||
# cli.show_server_banner = lambda *_: None
|
||||
|
||||
app = Flask(__name__)
|
||||
host = "0.0.0.0"
|
||||
port = 8090
|
||||
@@ -22,21 +30,33 @@ def allcmds():
|
||||
return KEY_WORD_DICT
|
||||
|
||||
|
||||
@app.route("/getversion", methods=["GET"])
|
||||
def getversion():
|
||||
log.debug("getversion %s", __version__)
|
||||
return {
|
||||
"version": __version__,
|
||||
}
|
||||
|
||||
|
||||
@app.route("/getvolume", methods=["GET"])
|
||||
def getvolume():
|
||||
volume = xiaomusic.get_volume_ret()
|
||||
return {
|
||||
"volume": xiaomusic.get_volume(),
|
||||
"volume": volume,
|
||||
}
|
||||
|
||||
|
||||
@app.route("/searchmusic", methods=["GET"])
|
||||
def searchmusic():
|
||||
name = request.args.get('name')
|
||||
name = request.args.get("name")
|
||||
return xiaomusic.searchmusic(name)
|
||||
|
||||
|
||||
@app.route("/playingmusic", methods=["GET"])
|
||||
def playingmusic():
|
||||
return xiaomusic.playingmusic()
|
||||
|
||||
|
||||
@app.route("/", methods=["GET"])
|
||||
def redirect_to_index():
|
||||
return send_from_directory("static", "index.html")
|
||||
@@ -53,6 +73,50 @@ async def do_cmd():
|
||||
return {"ret": "Unknow cmd"}
|
||||
|
||||
|
||||
@app.route("/getsetting", methods=["GET"])
|
||||
async def getsetting():
|
||||
config = xiaomusic.getconfig()
|
||||
log.debug(config)
|
||||
|
||||
alldevices = await xiaomusic.call_main_thread_function(xiaomusic.getalldevices)
|
||||
log.info(alldevices)
|
||||
data = {
|
||||
"mi_did": config.mi_did,
|
||||
"mi_did_list": alldevices["did_list"],
|
||||
"mi_hardware": config.hardware,
|
||||
"mi_hardware_list": alldevices["hardware_list"],
|
||||
"xiaomusic_search": config.search_prefix,
|
||||
"xiaomusic_proxy": config.proxy,
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@app.route("/savesetting", methods=["POST"])
|
||||
async def savesetting():
|
||||
data = request.get_json()
|
||||
log.info(data)
|
||||
await xiaomusic.saveconfig(data)
|
||||
return "save success"
|
||||
|
||||
|
||||
@app.route("/musiclist", methods=["GET"])
|
||||
async def musiclist():
|
||||
return xiaomusic.get_music_list()
|
||||
|
||||
|
||||
@app.route("/curplaylist", methods=["GET"])
|
||||
async def curplaylist():
|
||||
return xiaomusic.get_cur_play_list()
|
||||
|
||||
|
||||
@app.route("/delmusic", methods=["POST"])
|
||||
def delmusic():
|
||||
data = request.get_json()
|
||||
log.info(data)
|
||||
xiaomusic.del_music(data["name"])
|
||||
return "success"
|
||||
|
||||
|
||||
def static_path_handler(filename):
|
||||
log.debug(filename)
|
||||
log.debug(static_path)
|
||||
@@ -62,7 +126,7 @@ def static_path_handler(filename):
|
||||
|
||||
|
||||
def run_app():
|
||||
app.run(host=host, port=port)
|
||||
serve(app, host=host, port=port)
|
||||
|
||||
|
||||
def StartHTTPServer(_port, _static_path, _xiaomusic):
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
$(function(){
|
||||
$container=$("#cmds");
|
||||
append_op_button_name("下一首");
|
||||
append_op_button_name("全部循环");
|
||||
append_op_button_name("关机");
|
||||
append_op_button_name("单曲循环");
|
||||
append_op_button_name("播放歌曲");
|
||||
append_op_button_name("随机播放");
|
||||
append_op_button_name("刷新列表");
|
||||
append_op_button_name("下一首");
|
||||
append_op_button_name("关机");
|
||||
|
||||
$container.append($("<hr>"));
|
||||
|
||||
@@ -14,11 +14,73 @@ $(function(){
|
||||
append_op_button_name("60分钟后关机");
|
||||
|
||||
// 拉取声音
|
||||
sendcmd("get_volume#");
|
||||
$.get("/getvolume", function(data, status) {
|
||||
console.log(data, status, data["volume"]);
|
||||
$("#volume").val(data.volume);
|
||||
});
|
||||
|
||||
// 拉取版本
|
||||
$.get("/getversion", function(data, status) {
|
||||
console.log(data, status, data["version"]);
|
||||
$("#version").text(`(${data.version})`);
|
||||
});
|
||||
|
||||
// 拉取播放列表
|
||||
function refresh_music_list() {
|
||||
$('#music_list').empty();
|
||||
$.get("/musiclist", function(data, status) {
|
||||
console.log(data, status);
|
||||
$.each(data, function(key, value) {
|
||||
$('#music_list').append($('<option></option>').val(key).text(key));
|
||||
});
|
||||
|
||||
$('#music_list').change(function() {
|
||||
const selectedValue = $(this).val();
|
||||
$('#music_name').empty();
|
||||
$.each(data[selectedValue], function(index, item) {
|
||||
$('#music_name').append($('<option></option>').val(item).text(item));
|
||||
});
|
||||
});
|
||||
|
||||
$('#music_list').trigger('change');
|
||||
|
||||
// 获取当前播放列表
|
||||
$.get("curplaylist", function(data, status) {
|
||||
$('#music_list').val(data);
|
||||
$('#music_list').trigger('change');
|
||||
})
|
||||
})
|
||||
}
|
||||
refresh_music_list();
|
||||
|
||||
$("#play_music_list").on("click", () => {
|
||||
var music_list = $("#music_list").val();
|
||||
var music_name = $("#music_name").val();
|
||||
let cmd = "播放列表" + music_list + "|" + music_name;
|
||||
sendcmd(cmd);
|
||||
});
|
||||
|
||||
$("#del_music").on("click", () => {
|
||||
var del_music_name = $("#music_name").val();
|
||||
if (confirm(`确定删除歌曲 ${del_music_name} 吗?`)) {
|
||||
console.log(`删除歌曲 ${del_music_name}`);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/delmusic',
|
||||
data: JSON.stringify({"name": del_music_name}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: () => {
|
||||
alert(`删除 ${del_music_name} 成功`);
|
||||
refresh_music_list();
|
||||
},
|
||||
error: () => {
|
||||
alert(`删除 ${del_music_name} 失败`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function append_op_button_name(name) {
|
||||
append_op_button(name, name);
|
||||
}
|
||||
@@ -40,8 +102,8 @@ $(function(){
|
||||
|
||||
$("#play").on("click", () => {
|
||||
var search_key = $("#music-name").val();
|
||||
var filename=$("#music-filename").val();
|
||||
let cmd = "播放歌曲"+search_key+"|"+filename;
|
||||
var filename = $("#music-filename").val();
|
||||
let cmd = "播放歌曲" + search_key + "|" + filename;
|
||||
sendcmd(cmd);
|
||||
});
|
||||
|
||||
@@ -54,10 +116,12 @@ $(function(){
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/cmd",
|
||||
contentType: "application/json",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({cmd: cmd}),
|
||||
success: () => {
|
||||
// 请求成功时执行的操作
|
||||
if (cmd == "刷新列表") {
|
||||
setTimeout(refresh_music_list, 3000);
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
// 请求失败时执行的操作
|
||||
|
||||
@@ -1,74 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="/static/jquery-3.7.1.min.js"></script>
|
||||
<script src="/static/app.js"></script>
|
||||
<style>
|
||||
button {
|
||||
margin: 10px;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
background-color: #008CBA;
|
||||
}
|
||||
button:active {
|
||||
font-weight:bold;
|
||||
background-color: #007CBA;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
input {
|
||||
margin: 10px;
|
||||
width: 300px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.container{
|
||||
width: 280px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
@keyframes text-scroll {
|
||||
0% {
|
||||
left: 100%;
|
||||
}
|
||||
25% {
|
||||
left: 50%;
|
||||
}
|
||||
50% {
|
||||
left: 0%;
|
||||
}
|
||||
75% {
|
||||
left: -50%;
|
||||
}
|
||||
100% {
|
||||
left: -100%;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
animation: text-scroll 10s linear infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>小爱音箱操控面板</h2>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>小爱音箱操控面板<span id="version">(版本未知)</span></h2>
|
||||
<hr>
|
||||
<div id="cmds">
|
||||
</div>
|
||||
<hr>
|
||||
<div style="margin-left: 20px;">
|
||||
<div style="margin: 20px;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#8e43e7" style="height: 48px; width: 48px;"><path d="M550.826667 154.666667a47.786667 47.786667 0 0 0-19.84 4.48L298.666667 298.666667H186.453333A80 80 0 0 0 106.666667 378.453333v267.093334A80 80 0 0 0 186.453333 725.333333H298.666667l232.32 139.52a47.786667 47.786667 0 0 0 19.84 4.48A46.506667 46.506667 0 0 0 597.333333 822.826667V201.173333a46.506667 46.506667 0 0 0-46.506666-46.506666zM554.666667 822.826667c0 3.413333-3.84 3.84-3.84 3.84L320 688.853333l-9.6-6.186666H186.453333A37.12 37.12 0 0 1 149.333333 645.546667V378.453333A37.12 37.12 0 0 1 186.453333 341.333333h123.946667l10.24-6.186666 229.546667-137.6s3.84 0 3.84 3.84zM667.52 346.026667a21.333333 21.333333 0 0 0 0 30.293333 192 192 0 0 1 0 271.36 21.333333 21.333333 0 0 0 0 30.293333 21.333333 21.333333 0 0 0 30.293333 0 234.666667 234.666667 0 0 0 0-331.946666 21.333333 21.333333 0 0 0-30.293333 0z"></path><path d="M804.48 219.52a21.333333 21.333333 0 0 0-30.293333 30.293333 370.986667 370.986667 0 0 1 0 524.373334 21.333333 21.333333 0 0 0 0 30.293333 21.333333 21.333333 0 0 0 30.293333 0 414.08 414.08 0 0 0 0-584.96z"></path></svg>
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#8e43e7" style="height: 48px; width: 48px;"><path d="M550.826667 154.666667a47.786667 47.786667 0 0 0-19.84 4.48L298.666667 298.666667H186.453333A80 80 0 0 0 106.666667 378.453333v267.093334A80 80 0 0 0 186.453333 725.333333H298.666667l232.32 139.52a47.786667 47.786667 0 0 0 19.84 4.48A46.506667 46.506667 0 0 0 597.333333 822.826667V201.173333a46.506667 46.506667 0 0 0-46.506666-46.506666zM554.666667 822.826667c0 3.413333-3.84 3.84-3.84 3.84L320 688.853333l-9.6-6.186666H186.453333A37.12 37.12 0 0 1 149.333333 645.546667V378.453333A37.12 37.12 0 0 1 186.453333 341.333333h123.946667l10.24-6.186666 229.546667-137.6s3.84 0 3.84 3.84zM667.52 346.026667a21.333333 21.333333 0 0 0 0 30.293333 192 192 0 0 1 0 271.36 21.333333 21.333333 0 0 0 0 30.293333 21.333333 21.333333 0 0 0 30.293333 0 234.666667 234.666667 0 0 0 0-331.946666 21.333333 21.333333 0 0 0-30.293333 0z"></path><path d="M804.48 219.52a21.333333 21.333333 0 0 0-30.293333 30.293333 370.986667 370.986667 0 0 1 0 524.373334 21.333333 21.333333 0 0 0 0 30.293333 21.333333 21.333333 0 0 0 30.293333 0 414.08 414.08 0 0 0 0-584.96z"></path></svg>
|
||||
<input id="volume" type="range"></input>
|
||||
<a href="/static/setting.html">
|
||||
<svg fill="#8e43e7" height="64px" width="64px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-11.88 -11.88 77.76 77.76" xml:space="preserve" stroke="#8e43e7" transform="rotate(0)matrix(1, 0, 0, 1, 0, 0)" stroke-width="0.00054"><g id="SVGRepo_bgCarrier" stroke-width="0" transform="translate(0,0), scale(1)"><rect x="-11.88" y="-11.88" width="77.76" height="77.76" rx="18.6624" fill="#addcff" strokewidth="0"></rect></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="1.512"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M51.22,21h-5.052c-0.812,0-1.481-0.447-1.792-1.197s-0.153-1.54,0.42-2.114l3.572-3.571 c0.525-0.525,0.814-1.224,0.814-1.966c0-0.743-0.289-1.441-0.814-1.967l-4.553-4.553c-1.05-1.05-2.881-1.052-3.933,0l-3.571,3.571 c-0.574,0.573-1.366,0.733-2.114,0.421C33.447,9.313,33,8.644,33,7.832V2.78C33,1.247,31.753,0,30.22,0H23.78 C22.247,0,21,1.247,21,2.78v5.052c0,0.812-0.447,1.481-1.197,1.792c-0.748,0.313-1.54,0.152-2.114-0.421l-3.571-3.571 c-1.052-1.052-2.883-1.05-3.933,0l-4.553,4.553c-0.525,0.525-0.814,1.224-0.814,1.967c0,0.742,0.289,1.44,0.814,1.966l3.572,3.571 c0.573,0.574,0.73,1.364,0.42,2.114S8.644,21,7.832,21H2.78C1.247,21,0,22.247,0,23.78v6.439C0,31.753,1.247,33,2.78,33h5.052 c0.812,0,1.481,0.447,1.792,1.197s0.153,1.54-0.42,2.114l-3.572,3.571c-0.525,0.525-0.814,1.224-0.814,1.966 c0,0.743,0.289,1.441,0.814,1.967l4.553,4.553c1.051,1.051,2.881,1.053,3.933,0l3.571-3.572c0.574-0.573,1.363-0.731,2.114-0.42 c0.75,0.311,1.197,0.98,1.197,1.792v5.052c0,1.533,1.247,2.78,2.78,2.78h6.439c1.533,0,2.78-1.247,2.78-2.78v-5.052 c0-0.812,0.447-1.481,1.197-1.792c0.751-0.312,1.54-0.153,2.114,0.42l3.571,3.572c1.052,1.052,2.883,1.05,3.933,0l4.553-4.553 c0.525-0.525,0.814-1.224,0.814-1.967c0-0.742-0.289-1.44-0.814-1.966l-3.572-3.571c-0.573-0.574-0.73-1.364-0.42-2.114 S45.356,33,46.168,33h5.052c1.533,0,2.78-1.247,2.78-2.78V23.78C54,22.247,52.753,21,51.22,21z M52,30.22 C52,30.65,51.65,31,51.22,31h-5.052c-1.624,0-3.019,0.932-3.64,2.432c-0.622,1.5-0.295,3.146,0.854,4.294l3.572,3.571 c0.305,0.305,0.305,0.8,0,1.104l-4.553,4.553c-0.304,0.304-0.799,0.306-1.104,0l-3.571-3.572c-1.149-1.149-2.794-1.474-4.294-0.854 c-1.5,0.621-2.432,2.016-2.432,3.64v5.052C31,51.65,30.65,52,30.22,52H23.78C23.35,52,23,51.65,23,51.22v-5.052 c0-1.624-0.932-3.019-2.432-3.64c-0.503-0.209-1.021-0.311-1.533-0.311c-1.014,0-1.997,0.4-2.761,1.164l-3.571,3.572 c-0.306,0.306-0.801,0.304-1.104,0l-4.553-4.553c-0.305-0.305-0.305-0.8,0-1.104l3.572-3.571c1.148-1.148,1.476-2.794,0.854-4.294 C10.851,31.932,9.456,31,7.832,31H2.78C2.35,31,2,30.65,2,30.22V23.78C2,23.35,2.35,23,2.78,23h5.052 c1.624,0,3.019-0.932,3.64-2.432c0.622-1.5,0.295-3.146-0.854-4.294l-3.572-3.571c-0.305-0.305-0.305-0.8,0-1.104l4.553-4.553 c0.304-0.305,0.799-0.305,1.104,0l3.571,3.571c1.147,1.147,2.792,1.476,4.294,0.854C22.068,10.851,23,9.456,23,7.832V2.78 C23,2.35,23.35,2,23.78,2h6.439C30.65,2,31,2.35,31,2.78v5.052c0,1.624,0.932,3.019,2.432,3.64 c1.502,0.622,3.146,0.294,4.294-0.854l3.571-3.571c0.306-0.305,0.801-0.305,1.104,0l4.553,4.553c0.305,0.305,0.305,0.8,0,1.104 l-3.572,3.571c-1.148,1.148-1.476,2.794-0.854,4.294c0.621,1.5,2.016,2.432,3.64,2.432h5.052C51.65,23,52,23.35,52,23.78V30.22z"></path> <path d="M27,18c-4.963,0-9,4.037-9,9s4.037,9,9,9s9-4.037,9-9S31.963,18,27,18z M27,34c-3.859,0-7-3.141-7-7s3.141-7,7-7 s7,3.141,7,7S30.859,34,27,34z"></path> </g> </g></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
@@ -81,5 +32,19 @@
|
||||
<div class="container">
|
||||
<div id="playering-music" class="text"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<label for="music_list">播放列表:</label>
|
||||
<select id="music_list"></select>
|
||||
<label for="music_name">歌曲:</label>
|
||||
<select id="music_name"></select>
|
||||
</div>
|
||||
<button id="play_music_list">播放列表歌曲</button>
|
||||
<button id="del_music">删除选中歌曲</button>
|
||||
|
||||
<footer>
|
||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
34
xiaomusic/static/setting.html
Normal file
34
xiaomusic/static/setting.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="/static/jquery-3.7.1.min.js"></script>
|
||||
<script src="/static/setting.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>小爱音箱设置面板<span id="version">(版本未知)</span></h2>
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<label for="mi_did">MI_DID:</label>
|
||||
<select id="mi_did"></select>
|
||||
<label for="mi_hardware">MI_HARDWARE:</label>
|
||||
<select id="mi_hardware"></select>
|
||||
<label for="xiaomusic_search">XIAOMUSIC_SEARCH:</label>
|
||||
<select id="xiaomusic_search">
|
||||
<option value="ytsearch:">ytsearch:</option>
|
||||
<option value="bilisearch:">bilisearch:</option>
|
||||
</select>
|
||||
<label for="xiaomusic_proxy">XIAOMUSIC_PROXY(ytsearch需要):</label>
|
||||
<input id="xiaomusic_proxy" type="text" placeholder="http://192.168.2.5:8080"></input>
|
||||
</div>
|
||||
<hr>
|
||||
<button onclick="location.href='/';">返回首页</button>
|
||||
<button id="save">保存</button>
|
||||
|
||||
<footer>
|
||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
73
xiaomusic/static/setting.js
Normal file
73
xiaomusic/static/setting.js
Normal file
@@ -0,0 +1,73 @@
|
||||
$(function(){
|
||||
// 拉取版本
|
||||
$.get("/getversion", function(data, status) {
|
||||
console.log(data, status, data["version"]);
|
||||
$("#version").text(`(${data.version})`);
|
||||
});
|
||||
|
||||
// 拉取现有配置
|
||||
$.get("/getsetting", function(data, status) {
|
||||
console.log(data, status);
|
||||
|
||||
var mi_did_div = $("#mi_did")
|
||||
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")
|
||||
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 != "") {
|
||||
$("#xiaomusic_search").val(data.xiaomusic_search);
|
||||
}
|
||||
|
||||
if (data.xiaomusic_proxy != "") {
|
||||
$("#xiaomusic_proxy").val(data.xiaomusic_proxy);
|
||||
}
|
||||
});
|
||||
|
||||
$("#save").on("click", () => {
|
||||
var mi_did = $("#mi_did").val();
|
||||
var mi_hardware = $("#mi_hardware").val();
|
||||
var xiaomusic_search = $("#xiaomusic_search").val();
|
||||
var xiaomusic_proxy = $("#xiaomusic_proxy").val();
|
||||
console.log("mi_did", mi_did);
|
||||
console.log("mi_hardware", mi_hardware);
|
||||
console.log("xiaomusic_search", xiaomusic_search);
|
||||
console.log("xiaomusic_proxy", xiaomusic_proxy);
|
||||
var data = {
|
||||
mi_did: mi_did,
|
||||
mi_hardware: mi_hardware,
|
||||
xiaomusic_search: xiaomusic_search,
|
||||
xiaomusic_proxy: xiaomusic_proxy,
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/savesetting",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: (msg) => {
|
||||
alert(msg);
|
||||
},
|
||||
error: (msg) => {
|
||||
alert(msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
69
xiaomusic/static/style.css
Normal file
69
xiaomusic/static/style.css
Normal file
@@ -0,0 +1,69 @@
|
||||
button {
|
||||
margin: 10px;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
background-color: #008CBA;
|
||||
}
|
||||
button:active {
|
||||
font-weight:bold;
|
||||
background-color: #007CBA;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
label {
|
||||
margin-left: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
input,select {
|
||||
margin: 10px;
|
||||
width: 300px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.container{
|
||||
width: 280px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
@keyframes text-scroll {
|
||||
0% {
|
||||
left: 100%;
|
||||
}
|
||||
25% {
|
||||
left: 50%;
|
||||
}
|
||||
50% {
|
||||
left: 0%;
|
||||
}
|
||||
75% {
|
||||
left: -50%;
|
||||
}
|
||||
100% {
|
||||
left: -100%;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
animation: text-scroll 10s linear infinite;
|
||||
}
|
||||
|
||||
.rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
footer {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from http.cookies import SimpleCookie
|
||||
from typing import AsyncIterator
|
||||
from urllib.parse import urlparse
|
||||
import difflib
|
||||
import re
|
||||
from collections.abc import AsyncIterator
|
||||
from http.cookies import SimpleCookie
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from requests.utils import cookiejar_from_dict
|
||||
|
||||
@@ -62,6 +60,7 @@ def validate_proxy(proxy_str: str) -> bool:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# 模糊搜索
|
||||
def fuzzyfinder(user_input, collection):
|
||||
return difflib.get_close_matches(user_input, collection, 10, cutoff=0.1)
|
||||
|
||||
@@ -3,34 +3,35 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import urllib.parse
|
||||
import traceback
|
||||
import mutagen
|
||||
from xiaomusic.httpserver import StartHTTPServer
|
||||
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
import mutagen
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from miservice import MiAccount, MiIOService, MiNAService, miio_command
|
||||
from rich import print
|
||||
from rich.logging import RichHandler
|
||||
|
||||
from xiaomusic import (
|
||||
__version__,
|
||||
)
|
||||
from xiaomusic.config import (
|
||||
COOKIE_TEMPLATE,
|
||||
LATEST_ASK_API,
|
||||
KEY_WORD_DICT,
|
||||
KEY_WORD_ARG_BEFORE_DICT,
|
||||
KEY_MATCH_ORDER,
|
||||
KEY_WORD_ARG_BEFORE_DICT,
|
||||
KEY_WORD_DICT,
|
||||
LATEST_ASK_API,
|
||||
SUPPORT_MUSIC_TYPE,
|
||||
Config,
|
||||
)
|
||||
from xiaomusic.httpserver import StartHTTPServer
|
||||
from xiaomusic.utils import (
|
||||
calculate_tts_elapse,
|
||||
parse_cookie_string,
|
||||
fuzzyfinder,
|
||||
parse_cookie_string,
|
||||
)
|
||||
|
||||
EOF = object()
|
||||
@@ -52,6 +53,7 @@ class XiaoMusic:
|
||||
self.miio_service = None
|
||||
self.polling_event = asyncio.Event()
|
||||
self.new_record_event = asyncio.Event()
|
||||
self.queue = queue.Queue()
|
||||
|
||||
self.music_path = config.music_path
|
||||
self.hostname = config.hostname
|
||||
@@ -68,22 +70,34 @@ class XiaoMusic:
|
||||
self.cur_music = ""
|
||||
self._next_timer = None
|
||||
self._timeout = 0
|
||||
self._volume = 50
|
||||
self._volume = 0
|
||||
self._all_music = {}
|
||||
self._play_list = []
|
||||
self._cur_play_list = ""
|
||||
self._music_list = {} # 播放列表 key 为目录名, value 为 play_list
|
||||
self._playing = False
|
||||
|
||||
# 关机定时器
|
||||
self._stop_timer = None
|
||||
|
||||
# setup logger
|
||||
logging.basicConfig(
|
||||
format=f"[{__version__}]\t%(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(rich_tracebacks=True)],
|
||||
)
|
||||
self.log = logging.getLogger("xiaomusic")
|
||||
self.log.setLevel(logging.DEBUG if config.verbose else logging.INFO)
|
||||
self.log.addHandler(RichHandler())
|
||||
self.log.debug(config)
|
||||
|
||||
# 尝试从设置里加载配置
|
||||
self.try_init_setting()
|
||||
|
||||
# 启动时重新生成一次播放列表
|
||||
self.gen_all_music_list()
|
||||
self._gen_all_music_list()
|
||||
|
||||
# 启动时初始化获取声音
|
||||
self.set_last_record("get_volume#")
|
||||
|
||||
self.log.debug("ffmpeg_location: %s", self.ffmpeg_location)
|
||||
|
||||
@@ -121,10 +135,7 @@ class XiaoMusic:
|
||||
self.mina_service = MiNAService(account)
|
||||
self.miio_service = MiIOService(account)
|
||||
|
||||
async def _init_data_hardware(self):
|
||||
if self.config.cookie:
|
||||
# if use cookie do not need init
|
||||
return
|
||||
async def try_update_device_id(self):
|
||||
hardware_data = await self.mina_service.device_list()
|
||||
# fix multi xiaoai problems we check did first
|
||||
# why we use this way to fix?
|
||||
@@ -143,9 +154,15 @@ class XiaoMusic:
|
||||
self.device_id = h.get("deviceID")
|
||||
break
|
||||
else:
|
||||
raise Exception(
|
||||
self.log.error(
|
||||
f"we have no hardware: {self.config.hardware} please use `micli mina` to check"
|
||||
)
|
||||
|
||||
async def _init_data_hardware(self):
|
||||
if self.config.cookie:
|
||||
# if use cookie do not need init
|
||||
return
|
||||
await self.try_update_device_id()
|
||||
if not self.config.mi_did:
|
||||
devices = await self.miio_service.device_list()
|
||||
try:
|
||||
@@ -155,7 +172,7 @@ class XiaoMusic:
|
||||
if d["model"].endswith(self.config.hardware.lower())
|
||||
)
|
||||
except StopIteration:
|
||||
raise Exception(
|
||||
self.log.error(
|
||||
f"cannot find did for hardware: {self.config.hardware} "
|
||||
"please set it via MI_DID env"
|
||||
)
|
||||
@@ -225,11 +242,11 @@ class XiaoMusic:
|
||||
}
|
||||
self.new_record_event.set()
|
||||
|
||||
async def do_tts(self, value, wait_for_finish=False):
|
||||
async def do_tts(self, value):
|
||||
self.log.info("do_tts: %s", value)
|
||||
|
||||
if self.config.mute_xiaoai:
|
||||
await self.stop_if_xiaoai_is_playing()
|
||||
await self.force_stop_xiaoai()
|
||||
else:
|
||||
# waiting for xiaoai speaker done
|
||||
await asyncio.sleep(8)
|
||||
@@ -245,13 +262,11 @@ class XiaoMusic:
|
||||
self.config.mi_did,
|
||||
f"{self.config.tts_command} {value}",
|
||||
)
|
||||
if wait_for_finish:
|
||||
elapse = calculate_tts_elapse(value)
|
||||
await asyncio.sleep(elapse)
|
||||
await self.wait_for_tts_finish()
|
||||
|
||||
async def do_set_volume(self, value):
|
||||
value = int(value)
|
||||
self._volume = value
|
||||
self.log.info(f"声音设置为{value}")
|
||||
if not self.config.use_command:
|
||||
try:
|
||||
self.log.debug("do_set_volume not use_command value:%d", value)
|
||||
@@ -266,39 +281,17 @@ class XiaoMusic:
|
||||
f"{self.config.volume_command}=#{value}",
|
||||
)
|
||||
|
||||
async def wait_for_tts_finish(self):
|
||||
while True:
|
||||
if not await self.get_if_xiaoai_is_playing():
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
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
|
||||
await self.mina_service.player_pause(self.device_id)
|
||||
|
||||
async def wakeup_xiaoai(self):
|
||||
return await miio_command(
|
||||
self.miio_service,
|
||||
self.config.mi_did,
|
||||
f"{self.config.wakeup_command} {WAKEUP_KEYWORD} 0",
|
||||
)
|
||||
async def force_stop_xiaoai(self):
|
||||
await self.mina_service.player_stop(self.device_id)
|
||||
|
||||
# 是否在下载中
|
||||
def is_downloading(self):
|
||||
if not self.download_proc:
|
||||
return False
|
||||
if self.download_proc.returncode != None and self.download_proc.returncode < 0:
|
||||
if (
|
||||
self.download_proc.returncode is not None
|
||||
and self.download_proc.returncode < 0
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -350,9 +343,16 @@ class XiaoMusic:
|
||||
return f"http://{self.hostname}:{self.port}/{encoded_name}"
|
||||
|
||||
# 递归获取目录下所有歌曲,生成随机播放列表
|
||||
def gen_all_music_list(self):
|
||||
def _gen_all_music_list(self):
|
||||
self._all_music = {}
|
||||
all_music_by_dir = {}
|
||||
for root, dirs, filenames in os.walk(self.music_path):
|
||||
self.log.debug("root:%s dirs:%s music_path:%s", root, dirs, self.music_path)
|
||||
dir_name = os.path.basename(root)
|
||||
if self.music_path == root:
|
||||
dir_name = "其他"
|
||||
if dir_name not in all_music_by_dir:
|
||||
all_music_by_dir[dir_name] = {}
|
||||
for filename in filenames:
|
||||
self.log.debug("gen_all_music_list. filename:%s", filename)
|
||||
# 过滤隐藏文件
|
||||
@@ -371,10 +371,20 @@ class XiaoMusic:
|
||||
|
||||
# 歌曲名字相同会覆盖
|
||||
self._all_music[name] = os.path.join(root, filename)
|
||||
all_music_by_dir[dir_name][name] = True
|
||||
pass
|
||||
self._play_list = list(self._all_music.keys())
|
||||
self._cur_play_list = "全部"
|
||||
random.shuffle(self._play_list)
|
||||
self.log.debug(self._all_music)
|
||||
|
||||
self._music_list = {}
|
||||
self._music_list["全部"] = self._play_list
|
||||
for dir_name, musics in all_music_by_dir.items():
|
||||
self._music_list[dir_name] = list(musics.keys())
|
||||
self.log.debug("dir_name:%s, list:%s", dir_name, self._music_list[dir_name])
|
||||
pass
|
||||
|
||||
# 把下载的音乐加入播放列表
|
||||
def add_download_music(self, name):
|
||||
self._all_music[name] = os.path.join(self.music_path, f"{name}.mp3")
|
||||
@@ -387,7 +397,7 @@ class XiaoMusic:
|
||||
def get_next_music(self):
|
||||
play_list_len = len(self._play_list)
|
||||
if play_list_len == 0:
|
||||
self.log.warning(f"没有随机到歌曲")
|
||||
self.log.warning("没有随机到歌曲")
|
||||
return ""
|
||||
# 随机选择一个文件
|
||||
index = 0
|
||||
@@ -398,8 +408,13 @@ class XiaoMusic:
|
||||
next_index = index + 1
|
||||
if next_index >= play_list_len:
|
||||
next_index = 0
|
||||
filename = self._play_list[next_index]
|
||||
return filename
|
||||
name = self._play_list[next_index]
|
||||
filename = self.get_filename(name)
|
||||
if len(filename) <= 0:
|
||||
self._play_list.pop(next_index)
|
||||
self.log.info(f"pop not exist music:{name}")
|
||||
return self.get_next_music()
|
||||
return name
|
||||
|
||||
# 获取文件播放时长
|
||||
def get_file_duration(self, filename):
|
||||
@@ -416,7 +431,7 @@ class XiaoMusic:
|
||||
self.log.info(f"歌曲 {self.cur_music} : {filename} 的时长 {sec} 秒")
|
||||
if self._next_timer:
|
||||
self._next_timer.cancel()
|
||||
self.log.info(f"定时器已取消")
|
||||
self.log.info("定时器已取消")
|
||||
self._timeout = sec
|
||||
|
||||
async def _do_next():
|
||||
@@ -430,21 +445,32 @@ class XiaoMusic:
|
||||
self.log.info(f"{sec}秒后将会播放下一首")
|
||||
|
||||
async def run_forever(self):
|
||||
StartHTTPServer(self.port, self.music_path, self)
|
||||
async with ClientSession() as session:
|
||||
self.session = session
|
||||
await self.init_all_data(session)
|
||||
StartHTTPServer(self.port, self.music_path, self)
|
||||
task = asyncio.create_task(self.poll_latest_ask())
|
||||
assert task is not None # to keep the reference to task, do not remove this
|
||||
self.log.info(
|
||||
f"Running xiaomusic now, 用`{'/'.join(KEY_WORD_DICT.keys())}`开头来控制"
|
||||
)
|
||||
filtered_keywords = [
|
||||
keyword for keyword in KEY_MATCH_ORDER if "#" not in keyword
|
||||
]
|
||||
joined_keywords = "/".join(filtered_keywords)
|
||||
self.log.info(f"Running xiaomusic now, 用`{joined_keywords}`开头来控制")
|
||||
|
||||
while True:
|
||||
self.polling_event.set()
|
||||
await self.new_record_event.wait()
|
||||
self.new_record_event.clear()
|
||||
new_record = self.last_record
|
||||
if new_record is None:
|
||||
# 其他线程的函数调用
|
||||
try:
|
||||
func, callback, arg1 = self.queue.get(False)
|
||||
ret = await func(arg1=arg1)
|
||||
callback(ret)
|
||||
except queue.Empty:
|
||||
pass
|
||||
continue
|
||||
self.polling_event.clear() # stop polling when processing the question
|
||||
query = new_record.get("query", "").strip()
|
||||
ctrl_panel = new_record.get("ctrl_panel", False)
|
||||
@@ -522,7 +548,7 @@ class XiaoMusic:
|
||||
self.log.info("cur_music %s", self.cur_music)
|
||||
url = self.get_file_url(name)
|
||||
self.log.info("播放 %s", url)
|
||||
await self.stop_if_xiaoai_is_playing()
|
||||
await self.force_stop_xiaoai()
|
||||
await self.mina_service.play_by_url(self.device_id, url)
|
||||
self.log.info("已经开始播放了")
|
||||
# 设置下一首歌曲的播放定时器
|
||||
@@ -536,39 +562,76 @@ class XiaoMusic:
|
||||
if self.play_type == PLAY_TYPE_ALL or name == "":
|
||||
name = self.get_next_music()
|
||||
if name == "":
|
||||
await self.do_tts(f"本地没有歌曲")
|
||||
await self.do_tts("本地没有歌曲")
|
||||
return
|
||||
await self.play(arg1=name)
|
||||
|
||||
# 单曲循环
|
||||
async def set_play_type_one(self, **kwargs):
|
||||
self.play_type = PLAY_TYPE_ONE
|
||||
await self.do_tts(f"已经设置为单曲循环")
|
||||
await self.do_tts("已经设置为单曲循环")
|
||||
|
||||
# 全部循环
|
||||
async def set_play_type_all(self, **kwargs):
|
||||
self.play_type = PLAY_TYPE_ALL
|
||||
await self.do_tts(f"已经设置为全部循环")
|
||||
await self.do_tts("已经设置为全部循环")
|
||||
|
||||
# 随机播放
|
||||
async def random_play(self, **kwargs):
|
||||
self.play_type = PLAY_TYPE_ALL
|
||||
await self.do_tts(f"已经设置为全部循环并随机播放")
|
||||
# 重新生成随机播放列表
|
||||
self.gen_all_music_list()
|
||||
await self.play_next()
|
||||
random.shuffle(self._play_list)
|
||||
await self.do_tts("已经设置为随机播放")
|
||||
|
||||
# 刷新列表
|
||||
async def gen_music_list(self, **kwargs):
|
||||
self._gen_all_music_list()
|
||||
await self.do_tts("生成播放列表完毕")
|
||||
|
||||
# 删除歌曲
|
||||
def del_music(self, name):
|
||||
filename = self.get_filename(name)
|
||||
if filename == "":
|
||||
self.log.info(f"${name} not exist")
|
||||
return
|
||||
try:
|
||||
os.remove(filename)
|
||||
self.log.info(f"del ${filename} success")
|
||||
except OSError:
|
||||
self.log.error(f"del ${filename} failed")
|
||||
pass
|
||||
self._gen_all_music_list()
|
||||
|
||||
# 播放一个播放列表
|
||||
async def play_music_list(self, **kwargs):
|
||||
parts = kwargs["arg1"].split("|")
|
||||
list_name = parts[0]
|
||||
if list_name not in self._music_list:
|
||||
await self.do_tts(f"播放列表{list_name}不存在")
|
||||
return
|
||||
self._play_list = self._music_list[list_name]
|
||||
self._cur_play_list = list_name
|
||||
random.shuffle(self._play_list)
|
||||
self.log.info(f"开始播放列表{list_name}")
|
||||
|
||||
music_name = ""
|
||||
if len(parts) > 1:
|
||||
music_name = parts[1]
|
||||
else:
|
||||
music_name = self.get_next_music()
|
||||
await self.play(arg1=music_name)
|
||||
|
||||
async def stop(self, **kwargs):
|
||||
self._playing = False
|
||||
if self._next_timer:
|
||||
self._next_timer.cancel()
|
||||
self.log.info(f"定时器已取消")
|
||||
await self.stop_if_xiaoai_is_playing()
|
||||
self.log.info("定时器已取消")
|
||||
self.cur_music = ""
|
||||
await self.force_stop_xiaoai()
|
||||
|
||||
async def stop_after_minute(self, **kwargs):
|
||||
if self._stop_timer:
|
||||
self._stop_timer.cancel()
|
||||
self.log.info(f"关机定时器已取消")
|
||||
self.log.info("关机定时器已取消")
|
||||
minute = int(kwargs["arg1"])
|
||||
|
||||
async def _do_stop():
|
||||
@@ -584,19 +647,109 @@ class XiaoMusic:
|
||||
async def set_volume(self, **kwargs):
|
||||
value = kwargs["arg1"]
|
||||
await self.do_set_volume(value)
|
||||
self._volume = int(value)
|
||||
self.log.info(f"声音设置为{value}")
|
||||
|
||||
def get_volume(self):
|
||||
async def get_volume(self, **kwargs):
|
||||
playing_info = await self.mina_service.player_get_status(self.device_id)
|
||||
self.log.debug("get_volume. playing_info:%s", playing_info)
|
||||
self._volume = json.loads(playing_info.get("data", {}).get("info", "{}")).get(
|
||||
"volume", 5
|
||||
)
|
||||
self.log.info("get_volume. volume:%s", self._volume)
|
||||
|
||||
def get_volume_ret(self):
|
||||
return self._volume
|
||||
|
||||
# 搜索音乐
|
||||
def searchmusic(self, name):
|
||||
search_list = fuzzyfinder(name, self._play_list)
|
||||
all_music_list = list(self._all_music.keys())
|
||||
search_list = fuzzyfinder(name, all_music_list)
|
||||
self.log.debug("searchmusic. name:%s search_list:%s", name, search_list)
|
||||
return search_list
|
||||
|
||||
# 获取播放列表
|
||||
def get_music_list(self):
|
||||
return self._music_list
|
||||
|
||||
# 获取当前的播放列表
|
||||
def get_cur_play_list(self):
|
||||
return self._cur_play_list
|
||||
|
||||
# 正在播放中的音乐
|
||||
def playingmusic(self):
|
||||
self.log.debug("playingmusic. cur_music:%s", self.cur_music)
|
||||
return self.cur_music
|
||||
|
||||
# 获取当前配置
|
||||
def getconfig(self):
|
||||
return self.config
|
||||
|
||||
def try_init_setting(self):
|
||||
try:
|
||||
filename = os.path.join(self.music_path, "setting.json")
|
||||
with open(filename) as f:
|
||||
data = json.loads(f.read())
|
||||
self.update_config_from_setting(data)
|
||||
except FileNotFoundError:
|
||||
self.log.info(f"The file {filename} does not exist.")
|
||||
except json.JSONDecodeError:
|
||||
self.log.warning(f"The file {filename} contains invalid JSON.")
|
||||
|
||||
# 保存配置并重新启动
|
||||
async def saveconfig(self, data):
|
||||
# 默认暂时配置保存到 music 目录下
|
||||
filename = os.path.join(self.music_path, "setting.json")
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
self.update_config_from_setting(data)
|
||||
await self.call_main_thread_function(self.reinit)
|
||||
|
||||
def update_config_from_setting(self, data):
|
||||
self.config.mi_did = data["mi_did"]
|
||||
self.config.hardware = data["mi_hardware"]
|
||||
self.config.search_prefix = data["xiaomusic_search"]
|
||||
self.config.proxy = data["xiaomusic_proxy"]
|
||||
|
||||
self.search_prefix = self.config.search_prefix
|
||||
self.proxy = self.config.proxy
|
||||
self.log.info("update_config_from_setting ok. data:%s", data)
|
||||
|
||||
# 重新初始化
|
||||
async def reinit(self, **kwargs):
|
||||
await self.try_update_device_id()
|
||||
self.log.info("reinit success")
|
||||
|
||||
# 获取所有设备
|
||||
async def getalldevices(self, **kwargs):
|
||||
arg1 = kwargs["arg1"]
|
||||
self.log.debug("getalldevices. arg1:%s", arg1)
|
||||
did_list = []
|
||||
hardware_list = []
|
||||
hardware_data = await self.mina_service.device_list()
|
||||
for h in hardware_data:
|
||||
did = h.get("miotDID", "")
|
||||
if did != "":
|
||||
did_list.append(did)
|
||||
hardware = h.get("hardware", "")
|
||||
if h.get("hardware", "") != "":
|
||||
hardware_list.append(hardware)
|
||||
alldevices = {
|
||||
"did_list": did_list,
|
||||
"hardware_list": hardware_list,
|
||||
}
|
||||
return alldevices
|
||||
|
||||
# 用于在web线程里调用
|
||||
# 获取所有设备
|
||||
async def call_main_thread_function(self, func, arg1=None):
|
||||
loop = asyncio.get_event_loop()
|
||||
future = loop.create_future()
|
||||
|
||||
def callback(ret):
|
||||
nonlocal future
|
||||
loop.call_soon_threadsafe(future.set_result, ret)
|
||||
|
||||
self.queue.put((func, callback, arg1))
|
||||
self.last_record = None
|
||||
self.new_record_event.set()
|
||||
result = await future
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user