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

Compare commits

...

24 Commits

Author SHA1 Message Date
涵曦
b32e2e29d8 update version 2024-01-28 18:35:04 +08:00
涵曦
8f22e2c66e update requirements 2024-01-28 18:34:34 +08:00
涵曦
8801bb73f4 update readme 2024-01-28 18:29:14 +08:00
涵曦
82a215e3bc update version 2024-01-28 18:26:28 +08:00
涵曦
88e5d98c68 新增输入框输入歌曲名 2024-01-28 18:25:22 +08:00
涵曦
c7ab57c06a 新增定时关机命令,新增控制面板界面 2024-01-28 18:17:45 +08:00
涵曦
f1938b9096 update readme 2024-01-27 23:09:30 +08:00
涵曦
5242deea33 update version 2024-01-27 23:00:31 +08:00
涵曦
d06b3cd2a5 新增随机播放指令 2024-01-27 23:00:17 +08:00
涵曦
2a59d1f69c 推送docker修改 2024-01-27 22:44:23 +08:00
涵曦
358f7ccb98 推送docker修改 2024-01-27 22:41:02 +08:00
涵曦
1043b6f32f update version to v0.1.4 2024-01-27 22:31:13 +08:00
涵曦
8f2d26ac10 修复本地flac音乐播放后去下载错误音乐的问题 2024-01-27 22:26:53 +08:00
涵曦
8e8a605816 支持flac格式的本地文件 2024-01-27 20:56:07 +08:00
涵曦
63eb0c22cb bugfix: kill download progress don't raise error 2023-10-16 22:53:02 +08:00
涵曦
ca07ed0dd3 fix: error when play next 2023-10-16 22:41:00 +08:00
涵曦
7d158ff40e some feat 2023-10-16 22:40:21 +08:00
涵曦
90243b395a Merge pull request #3 from dzhuang/allow_hardware_param
Use MI_HARDWARE in env.
2023-10-16 19:25:11 +08:00
涵曦
653bd417e5 Merge branch 'main' into allow_hardware_param 2023-10-16 19:24:51 +08:00
涵曦
6d89a24b28 Merge pull request #4 from dzhuang/set_proxy_to_null
Default proxy to None.
2023-10-16 19:17:30 +08:00
dzhuang
0ca2adb014 Default proxy to None. 2023-10-16 18:13:09 +08:00
dzhuang
a8a9e2bc45 Use MI_HARDWARE in env. 2023-10-16 18:07:07 +08:00
涵曦
7e7bb256b5 Update README.md 2023-10-15 21:41:12 +08:00
涵曦
35c43646cb Update README.md 2023-10-15 21:40:13 +08:00
12 changed files with 579 additions and 105 deletions

View File

@@ -63,4 +63,4 @@ jobs:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: hanxi/xiaomusic:${{ github.ref_name }}
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:latest, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:stable

View File

@@ -33,14 +33,21 @@ pdm run xiaomusic.py
```txt
"L07A": ("5-1", "5-5"), # Redmi小爱音箱Play(l7a)
````
## 支持音乐格式
- mp3
- flac
> 本地音乐会搜索 mp3 和 flac 格式的文件,下载的歌曲是 mp3 格式的。
## 在 Docker 里使用
```shell
docker run -e MI_USER=<your-xiaomi-account> -e MI_PASS=<your-xiaomi-password> -e MI_DID=<your-xiaomi-speaker-mid> -e XIAOMUSIC_PROXY=<proxy-for-yt-dlp> -e XIAOMUSIC_HOSTNAME=192.168.2.5 -p 8090:8090 -v ./music:/app/music hanxi/xiaomusic --hardware=<L07A> -
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 -p 8090:8090 -v ./music:/app/music hanxi/xiaomusic
```
- XIAOMUSIC_PROXY 用于配置代理yt-dlp 工具下载歌曲会用到。
- XIAOMUSIC_PROXY 用于配置代理,默认为空,yt-dlp 工具下载歌曲会用到。
- MI_HARDWARE 是小米音箱的型号,默认为'L07A'
- 注意端口必须映射为与容器内一致XIAOMUSIC_HOSTNAME 需要设置为宿主机的 IP 地址,否则小爱无法正常播放。
- 可以把 /app/music 目录映射到本地,用于保存下载的歌曲。
@@ -50,6 +57,13 @@ docker run -e MI_USER=<your-xiaomi-account> -e MI_PASS=<your-xiaomi-password> -e
docker build -t xiaomusic .
```
## 简易的控制面板
浏览器进入 <http://192.168.2.5:8090>
- ip 是 XIAOMUSIC_HOSTNAME 设置的
- 8090 是默认端口
## 感谢
- [xiaomi](https://www.mi.com/)

165
pdm.lock generated
View File

@@ -6,7 +6,7 @@ groups = ["default"]
cross_platform = true
static_urls = false
lock_version = "4.3"
content_hash = "sha256:c123354af86c15de519dfa703c59361f09898eb635df198f3dcf9ef6ed41ffba"
content_hash = "sha256:091ebbfd2c575745f3dae0440e7aaa775c6e2121050f4c101b0676a0bcef7606"
[[package]]
name = "aiohttp"
@@ -69,6 +69,19 @@ files = [
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
]
[[package]]
name = "asgiref"
version = "3.7.2"
requires_python = ">=3.7"
summary = "ASGI specs, helper code, and adapters"
dependencies = [
"typing-extensions>=4; python_version < \"3.11\"",
]
files = [
{file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
{file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
]
[[package]]
name = "async-timeout"
version = "4.0.3"
@@ -89,6 +102,16 @@ files = [
{file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
]
[[package]]
name = "blinker"
version = "1.7.0"
requires_python = ">=3.8"
summary = "Fast, simple object-to-object and broadcast signaling"
files = [
{file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
{file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
]
[[package]]
name = "brotli"
version = "1.1.0"
@@ -280,6 +303,61 @@ files = [
{file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"},
]
[[package]]
name = "click"
version = "8.1.7"
requires_python = ">=3.7"
summary = "Composable command line interface toolkit"
dependencies = [
"colorama; platform_system == \"Windows\"",
]
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[[package]]
name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "flask"
version = "3.0.1"
requires_python = ">=3.8"
summary = "A simple framework for building complex web applications."
dependencies = [
"Jinja2>=3.1.2",
"Werkzeug>=3.0.0",
"blinker>=1.6.2",
"click>=8.1.3",
"itsdangerous>=2.1.2",
]
files = [
{file = "flask-3.0.1-py3-none-any.whl", hash = "sha256:ca631a507f6dfe6c278ae20112cea3ff54ff2216390bf8880f6b035a5354af13"},
{file = "flask-3.0.1.tar.gz", hash = "sha256:6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403"},
]
[[package]]
name = "flask"
version = "3.0.1"
extras = ["async"]
requires_python = ">=3.8"
summary = "A simple framework for building complex web applications."
dependencies = [
"asgiref>=3.2",
"flask==3.0.1",
]
files = [
{file = "flask-3.0.1-py3-none-any.whl", hash = "sha256:ca631a507f6dfe6c278ae20112cea3ff54ff2216390bf8880f6b035a5354af13"},
{file = "flask-3.0.1.tar.gz", hash = "sha256:6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403"},
]
[[package]]
name = "frozenlist"
version = "1.4.0"
@@ -329,6 +407,29 @@ files = [
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
[[package]]
name = "itsdangerous"
version = "2.1.2"
requires_python = ">=3.7"
summary = "Safely pass data to untrusted environments and back."
files = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
]
[[package]]
name = "jinja2"
version = "3.1.3"
requires_python = ">=3.7"
summary = "A very fast and expressive template engine."
dependencies = [
"MarkupSafe>=2.0",
]
files = [
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
@@ -342,6 +443,45 @@ files = [
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[[package]]
name = "markupsafe"
version = "2.1.4"
requires_python = ">=3.7"
summary = "Safely add untrusted strings to HTML/XML markup."
files = [
{file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"},
{file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"},
{file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"},
{file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"},
{file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"},
{file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"},
{file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"},
{file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"},
{file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"},
{file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"},
{file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"},
{file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"},
{file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"},
{file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"},
{file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"},
{file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"},
{file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"},
{file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"},
{file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"},
{file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"},
{file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"},
{file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"},
{file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"},
{file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"},
{file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"},
{file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"},
{file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"},
{file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"},
{file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"},
{file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"},
{file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"},
]
[[package]]
name = "mdurl"
version = "0.1.2"
@@ -493,6 +633,16 @@ files = [
{file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"},
]
[[package]]
name = "typing-extensions"
version = "4.9.0"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
files = [
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
]
[[package]]
name = "urllib3"
version = "2.0.6"
@@ -550,6 +700,19 @@ files = [
{file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
]
[[package]]
name = "werkzeug"
version = "3.0.1"
requires_python = ">=3.8"
summary = "The comprehensive WSGI web application library."
dependencies = [
"MarkupSafe>=2.1.1",
]
files = [
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
]
[[package]]
name = "yarl"
version = "1.9.2"

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.1.3"
version = "0.1.7"
description = "Play Music with xiaomi AI speaker"
authors = [
{name = "涵曦", email = "im.hanxi@gmail.com"},
@@ -12,6 +12,7 @@ dependencies = [
"miservice-fork>=2.2.1",
"mutagen>=1.47.0",
"yt-dlp>=2023.10.13",
"flask[async]>=3.0.1",
]
requires-python = ">=3.10"
readme = "README.md"

View File

@@ -36,12 +36,18 @@ aiohttp==3.8.6 \
aiosignal==1.3.1 \
--hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \
--hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17
asgiref==3.7.2 \
--hash=sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e \
--hash=sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed
async-timeout==4.0.3 \
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
attrs==23.1.0 \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
blinker==1.7.0 \
--hash=sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 \
--hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182
brotli==1.1.0 \
--hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \
--hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \
@@ -131,6 +137,12 @@ charset-normalizer==3.3.0 \
--hash=sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293 \
--hash=sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e \
--hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e
click==8.1.7 \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
flask==3.0.1 \
--hash=sha256:6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403 \
--hash=sha256:ca631a507f6dfe6c278ae20112cea3ff54ff2216390bf8880f6b035a5354af13
frozenlist==1.4.0 \
--hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \
--hash=sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251 \
@@ -166,9 +178,47 @@ frozenlist==1.4.0 \
idna==3.4 \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
itsdangerous==2.1.2 \
--hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \
--hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a
Jinja2==3.1.3 \
--hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \
--hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90
markdown-it-py==3.0.0 \
--hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \
--hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb
MarkupSafe==2.1.4 \
--hash=sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69 \
--hash=sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0 \
--hash=sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d \
--hash=sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74 \
--hash=sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d \
--hash=sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f \
--hash=sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6 \
--hash=sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656 \
--hash=sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc \
--hash=sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56 \
--hash=sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc \
--hash=sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250 \
--hash=sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc \
--hash=sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863 \
--hash=sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8 \
--hash=sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f \
--hash=sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2 \
--hash=sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e \
--hash=sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e \
--hash=sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb \
--hash=sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26 \
--hash=sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131 \
--hash=sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858 \
--hash=sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e \
--hash=sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84 \
--hash=sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7 \
--hash=sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea \
--hash=sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b \
--hash=sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6 \
--hash=sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475 \
--hash=sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74
mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
@@ -241,6 +291,9 @@ requests==2.31.0 \
rich==13.6.0 \
--hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \
--hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef
typing-extensions==4.9.0 \
--hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \
--hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd
urllib3==2.0.6 \
--hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \
--hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564
@@ -284,6 +337,9 @@ websockets==11.0.3 \
--hash=sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602 \
--hash=sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf \
--hash=sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564
Werkzeug==3.0.1 \
--hash=sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc \
--hash=sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10
yarl==1.9.2 \
--hash=sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571 \
--hash=sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7 \

1
update-requirements.sh Normal file
View File

@@ -0,0 +1 @@
pdm export -o requirements.txt

View File

@@ -37,15 +37,41 @@ KEY_WORD_DICT = {
"播放歌曲": "play",
"放歌曲": "play",
"下一首": "play_next",
"单曲循环":"set_play_type_one",
"全部循环":"set_play_type_all",
"关机":"stop",
"停止播放":"stop",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "random_play",
"关机": "stop",
"停止播放": "stop",
"分钟后关机": "stop_after_minute",
}
# 命令参数在前面
KEY_WORD_ARG_BEFORE_DICT = {
"分钟后关机": True,
}
# 匹配优先级
KEY_MATCH_ORDER = [
"分钟后关机",
"播放歌曲",
"放歌曲",
"下一首",
"单曲循环",
"全部循环",
"随机播放",
"关机",
"停止播放",
]
SUPPORT_MUSIC_TYPE = [
"mp3",
"flac",
]
@dataclass
class Config:
hardware: str = "L07A"
hardware: str = os.getenv("MI_HARDWARE", "L07A")
account: str = os.getenv("MI_USER", "")
password: str = os.getenv("MI_PASS", "")
mi_did: str = os.getenv("MI_DID", "")
@@ -56,7 +82,7 @@ class Config:
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"))
proxy: str = os.getenv("XIAOMUSIC_PROXY", "http://192.168.2.5:8080")
proxy: str | None = os.getenv("XIAOMUSIC_PROXY", None)
def __post_init__(self) -> None:
if self.proxy:

68
xiaomusic/httpserver.py Normal file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python3
import os
import traceback
from flask import Flask, request, send_from_directory
from threading import Thread
from xiaomusic.config import (
KEY_WORD_DICT,
)
app = Flask(__name__)
host = "0.0.0.0"
port = 8090
static_path = "music"
xiaomusic = None
log = None
@app.route("/allcmds")
def allcmds():
return KEY_WORD_DICT
@app.route("/", methods=["GET"])
def redirect_to_index():
return send_from_directory("static", "index.html")
@app.route("/cmd", methods=["POST"])
async def do_cmd():
data = request.get_json()
cmd = data.get("cmd")
if len(cmd) > 0:
log.debug("docmd. cmd:%s", cmd)
xiaomusic.set_last_record(cmd)
return {"ret": "OK"}
return {"ret": "Unknow cmd"}
def static_path_handler(filename):
log.debug(filename)
log.debug(static_path)
absolute_path = os.path.abspath(static_path)
log.debug(absolute_path)
return send_from_directory(absolute_path, filename)
def run_app():
app.run(host=host, port=port)
def StartHTTPServer(_host, _port, _static_path, _xiaomusic):
global host, port, static_path, xiaomusic, log
host = _host
port = _port
static_path = _static_path
xiaomusic = _xiaomusic
log = xiaomusic.log
app.add_url_rule(
f"/{static_path}/<path:filename>", "static_path_handler", static_path_handler
)
server_thread = Thread(target=run_app)
server_thread.daemon = True
server_thread.start()
xiaomusic.log.info(f"Serving on {host}:{port}")

63
xiaomusic/static/app.js Normal file
View File

@@ -0,0 +1,63 @@
$(function(){
// 拉取所有可操作的命令
$.get("/allcmds", function(data, status) {
console.log(data, status);
$container=$("#cmds");
// 遍历数据
for (const [key, value] of Object.entries(data)) {
if (key != "分钟后关机" && key != "放歌曲") {
append_op_button(key);
}
}
append_op_button("5分钟后关机");
append_op_button("10分钟后关机");
append_op_button("30分钟后关机");
append_op_button("60分钟后关机");
});
function append_op_button(name) {
// 创建按钮
const $button = $("<button>");
$button.text(name);
$button.attr("type", "button");
// 设置按钮点击事件
$button.on("click", () => {
// 发起post请求
$.ajax({
type: "POST",
url: "/cmd",
contentType: "application/json",
data: JSON.stringify({cmd: name}),
success: () => {
// 请求成功时执行的操作
},
error: () => {
// 请求失败时执行的操作
}
});
});
// 添加按钮到容器
$container.append($button);
}
$("#play").on("click", () => {
name = $("#music-name").val();
let cmd = "播放歌曲"+name;
$.ajax({
type: "POST",
url: "/cmd",
contentType: "application/json",
data: JSON.stringify({cmd: cmd}),
success: () => {
// 请求成功时执行的操作
},
error: () => {
// 请求失败时执行的操作
}
});
});
});

View File

@@ -0,0 +1,30 @@
<!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/app.js"></script>
<style>
button {
margin: 10px;
width: 100px;
height: 50px;
}
input {
height: 40px;
}
</style>
</head>
<body>
<h2>小爱音箱操控面板</h2>
<hr>
<div id="cmds">
</div>
<hr>
<div>
<input id="music-name" type="text" placeholder="请输入歌曲名称"></input>
<button id="play">播放</button>
</div>
</body>
</html>

2
xiaomusic/static/jquery-3.7.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,22 +1,15 @@
#!/usr/bin/env python3
import asyncio
import functools
import http.server
import io
import json
import logging
import os
import random
import re
import shutil
import socket
import socketserver
import tempfile
import threading
import time
import urllib.parse
import traceback
import mutagen.mp3
import mutagen
from xiaomusic.httpserver import StartHTTPServer
from pathlib import Path
@@ -29,6 +22,9 @@ from xiaomusic.config import (
COOKIE_TEMPLATE,
LATEST_ASK_API,
KEY_WORD_DICT,
KEY_WORD_ARG_BEFORE_DICT,
KEY_MATCH_ORDER,
SUPPORT_MUSIC_TYPE,
Config,
)
from xiaomusic.utils import (
@@ -38,28 +34,9 @@ from xiaomusic.utils import (
EOF = object()
PLAY_TYPE_ONE = 0 # 单曲循环
PLAY_TYPE_ALL = 1 # 全部循环
PLAY_TYPE_ONE = 0 # 单曲循环
PLAY_TYPE_ALL = 1 # 全部循环
class ThreadedHTTPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
logger = logging.getLogger("xiaomusic")
def log_message(self, format, *args):
self.logger.debug(f"{self.address_string()} - {format}", *args)
def log_error(self, format, *args):
self.logger.error(f"{self.address_string()} - {format}", *args)
def copyfile(self, source, outputfile):
try:
super().copyfile(source, outputfile)
except (socket.error, ConnectionResetError, BrokenPipeError):
# ignore this or TODO find out why the error later
pass
class XiaoMusic:
def __init__(self, config: Config):
@@ -83,11 +60,14 @@ class XiaoMusic:
# 下载对象
self.download_proc = None
# 单曲循环,全部循环
self.play_type = PLAY_TYPE_ONE
self.play_type = PLAY_TYPE_ALL
self.cur_music = ""
self._next_timer = None
self._timeout = 0
# 关机定时器
self._stop_timer = None
# setup logger
self.log = logging.getLogger("xiaomusic")
self.log.setLevel(logging.DEBUG if config.verbose else logging.INFO)
@@ -98,16 +78,16 @@ class XiaoMusic:
async with ClientSession() as session:
session._cookie_jar = self.cookie_jar
while True:
self.log.debug(
"Listening new message, timestamp: %s", self.last_timestamp
)
# self.log.debug(
# "Listening new message, timestamp: %s", self.last_timestamp
# )
await self.get_latest_ask_from_xiaoai(session)
start = time.perf_counter()
self.log.debug("Polling_event, timestamp: %s", self.last_timestamp)
# self.log.debug("Polling_event, timestamp: %s", self.last_timestamp)
await self.polling_event.wait()
if (d := time.perf_counter() - start) < 1:
# sleep to avoid too many request
self.log.debug("Sleep %f, timestamp: %s", d, self.last_timestamp)
# self.log.debug("Sleep %f, timestamp: %s", d, self.last_timestamp)
await asyncio.sleep(1 - d)
async def init_all_data(self, session):
@@ -115,7 +95,7 @@ class XiaoMusic:
await self._init_data_hardware()
session.cookie_jar.update_cookies(self.get_cookie())
self.cookie_jar = session.cookie_jar
self.start_http_server()
StartHTTPServer(self.hostname, self.port, self.music_path, self)
async def login_miboy(self, session):
account = MiAccount(
@@ -225,6 +205,13 @@ class XiaoMusic:
self.last_record = last_record
self.new_record_event.set()
# 手动发消息
def set_last_record(self, query):
self.last_record = {
"query": query,
}
self.new_record_event.set()
async def do_tts(self, value, wait_for_finish=False):
self.log.info("do_tts: %s", value)
if not self.config.use_command:
@@ -249,17 +236,6 @@ class XiaoMusic:
break
await asyncio.sleep(1)
def start_http_server(self):
# create the server
handler = functools.partial(HTTPRequestHandler, directory=self.music_path)
httpd = ThreadedHTTPServer(("", self.port), handler)
# start the server in a new thread
server_thread = threading.Thread(target=httpd.serve_forever)
server_thread.daemon = True
server_thread.start()
self.log.info(f"Serving on {self.hostname}:{self.port}")
async def get_if_xiaoai_is_playing(self):
playing_info = await self.mina_service.player_get_status(self.device_id)
# WTF xiaomi api
@@ -286,63 +262,87 @@ class XiaoMusic:
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 != None and self.download_proc.returncode < 0:
return False
return True
# 下载歌曲
async def download(self, name):
if self.download_proc:
self.download_proc.kill()
try:
self.download_proc.kill()
except ProcessLookupError:
pass
self.download_proc = await asyncio.create_subprocess_exec(
"yt-dlp", f"ytsearch:{name}",
"-x", "--audio-format", "mp3",
"--paths", self.music_path,
"-o", f"{name}.mp3",
"--proxy", f"{self.proxy}",
"--ffmpeg-location", "./ffmpeg/bin")
sbp_args = (
"yt-dlp",
f"ytsearch:{name}",
"-x",
"--audio-format",
"mp3",
"--paths",
self.music_path,
"-o",
f"{name}.mp3",
"--ffmpeg-location",
"./ffmpeg/bin",
)
if self.proxy:
sbp_args += ("--proxy", f"{self.proxy}")
self.download_proc = await asyncio.create_subprocess_exec(*sbp_args)
await self.do_tts(f"正在下载歌曲{name}")
def get_filename(self, name):
filename = os.path.join(self.music_path, f"{name}.mp3")
filename = os.path.join(self.music_path, name)
return filename
# 本地是否存在歌曲
def local_exist(self, name):
filename = self.get_filename(name)
self.log.debug("local_exist. filename:%s", filename)
return os.path.exists(filename)
for tp in SUPPORT_MUSIC_TYPE:
filename = self.get_filename(f"{name}.{tp}")
self.log.debug("try local_exist. filename:%s", filename)
if os.path.exists(filename):
return filename
return ""
# 获取歌曲播放地址
def get_file_url(self, name):
encoded_name = urllib.parse.quote(os.path.basename(name))
return f"http://{self.hostname}:{self.port}/{encoded_name}.mp3"
def get_file_url(self, filename):
self.log.debug("get_file_url. filename:%s", filename)
encoded_name = urllib.parse.quote(filename)
return f"http://{self.hostname}:{self.port}/{encoded_name}"
# 随机获取一首音乐
def random_music(self):
files = os.listdir(self.music_path)
# 过滤 mp3 文件
mp3_files = [file for file in files if file.endswith(".mp3")]
if len(mp3_files) == 0:
# 过滤音乐文件
music_files = []
for file in files:
for tp in SUPPORT_MUSIC_TYPE:
if file.endswith(f".{tp}"):
music_files.append(file)
if len(music_files) == 0:
self.log.warning(f"没有随机到歌曲")
return ""
# 随机选择一个文件
mp3_file = random.choice(mp3_files)
name = mp3_file[:-4]
self.log.info(f"随机到歌曲{name}")
return name
music_file = random.choice(music_files)
(filename, extension) = os.path.splitext(music_file)
self.log.info(f"随机到歌曲{filename}{extension}")
return filename
# 获取mp3文件播放时长
def get_mp3_duration(self, name):
filename = self.get_filename(name)
audio = mutagen.mp3.MP3(filename)
return audio.info.length
# 获取文件播放时长
def get_file_duration(self, filename):
# 获取音频文件对象
audio = mutagen.File(filename)
# 获取播放时长
duration = audio.info.length
return duration
# 设置下一首歌曲的播放定时器
def set_next_music_timeout(self):
sec = int(self.get_mp3_duration(self.cur_music))
sec = int(self.get_file_duration(self.cur_music))
self.log.info(f"歌曲{self.cur_music}的时长{sec}")
if self._next_timer:
self._next_timer.cancel()
@@ -351,7 +351,10 @@ class XiaoMusic:
async def _do_next():
await asyncio.sleep(self._timeout)
await self.play_next()
try:
await self.play_next()
except Exception as e:
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
self._next_timer = asyncio.ensure_future(_do_next())
self.log.info(f"{sec}秒后将会播放下一首")
@@ -362,7 +365,10 @@ class XiaoMusic:
await self.init_all_data(session)
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())}`开头来控制")
self.log.info(
f"Running xiaomusic now, 用`{'/'.join(KEY_WORD_DICT.keys())}`开头来控制"
)
while True:
self.polling_event.set()
await self.new_record_event.wait()
@@ -373,8 +379,8 @@ class XiaoMusic:
self.log.debug("收到消息:%s", query)
# 匹配命令
match = re.match(rf"^({'|'.join(KEY_WORD_DICT.keys())})", query)
if not match:
opvalue, oparg = self.match_cmd(query)
if not opvalue:
await asyncio.sleep(1)
continue
@@ -384,32 +390,53 @@ class XiaoMusic:
# waiting for xiaoai speaker done
await asyncio.sleep(8)
opkey = match.groups()[0]
opvalue = KEY_WORD_DICT[opkey]
oparg = query[len(opkey):]
self.log.info("收到指令:%s %s", opkey, oparg)
try:
func = getattr(self, opvalue)
await func(name = oparg)
await func(arg1=oparg)
except Exception as e:
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
# 匹配命令
def match_cmd(self, query):
for opkey in KEY_MATCH_ORDER:
patternarg = rf"(.*){opkey}(.*)"
# 匹配参数
matcharg = re.match(patternarg, query)
if not matcharg:
continue
argpre = matcharg.groups()[0]
argafter = matcharg.groups()[1]
self.log.debug(
"matcharg. opkey:%s, argpre:%s, argafter:%s",
opkey,
argpre,
argafter,
)
oparg = argafter
opvalue = KEY_WORD_DICT[opkey]
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 (None, None)
# 播放歌曲
async def play(self, **kwargs):
name = kwargs["name"]
name = kwargs["arg1"]
if name == "":
await self.play_next()
return
await self.do_tts(f"即将播放{name}")
if not self.local_exist(name):
filename = self.local_exist(name)
if len(filename) <= 0:
await self.download(name)
self.log.info("正在下载中 %s", name)
filename = self.get_filename(f"{name}.mp3")
await self.download_proc.wait()
self.cur_music = name
url = self.get_file_url(name)
self.cur_music = filename
url = self.get_file_url(filename)
self.log.info("播放 %s", url)
await self.stop_if_xiaoai_is_playing()
await self.mina_service.play_by_url(self.device_id, url)
@@ -420,7 +447,8 @@ class XiaoMusic:
# 下一首
async def play_next(self, **kwargs):
self.log.info("下一首")
name = self.cur_music
(name, _) = os.path.splitext(os.path.basename(self.cur_music))
self.log.debug("play_next. name:%s, cur_music:%s", name, self.cur_music)
if self.play_type == PLAY_TYPE_ALL or name == "":
name = self.random_music()
if name == "":
@@ -438,8 +466,30 @@ class XiaoMusic:
self.play_type = PLAY_TYPE_ALL
await self.do_tts(f"已经设置为全部循环")
# 随机播放
async def random_play(self, **kwargs):
self.play_type = PLAY_TYPE_ALL
await self.do_tts(f"已经设置为全部循环并随机播放")
await self.play_next()
async def stop(self, **kwargs):
if self._next_timer:
self._next_timer.cancel()
self.log.info(f"定时器已取消")
await self.stop_if_xiaoai_is_playing()
async def stop_after_minute(self, **kwargs):
if self._stop_timer:
self._stop_timer.cancel()
self.log.info(f"关机定时器已取消")
minute = int(kwargs["arg1"])
async def _do_stop():
await asyncio.sleep(minute * 60)
try:
await self.stop()
except Exception as e:
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
self._stop_timer = asyncio.ensure_future(_do_stop())
self.log.info(f"{minute}分钟后将关机")