mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-06 14:52:50 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa698667c9 | ||
|
|
700e17854c | ||
|
|
40258c9fa1 | ||
|
|
eb59bf0db5 | ||
|
|
a9df78af97 | ||
|
|
901506a32d | ||
|
|
0ddbe58fbd | ||
|
|
350d82184f | ||
|
|
5b8054abd9 | ||
|
|
c5c691b653 | ||
|
|
7a44c8587c | ||
|
|
5092ffc91a | ||
|
|
2da12e12d5 | ||
|
|
5aff72dbb6 | ||
|
|
043f452e71 | ||
|
|
5cedf8a907 | ||
|
|
ae77c7232e | ||
|
|
d559413d46 | ||
|
|
8b185d8768 | ||
|
|
202105a11f |
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,3 +1,43 @@
|
||||
## v0.2.0 (2024-07-09)
|
||||
|
||||
### Feat
|
||||
|
||||
- 触屏版可以不用设置 XIAOMUSIC_USE_MUSIC_API
|
||||
- 升级依赖库
|
||||
- 唤醒口令配置支持配语音词,简化自定义口令配置 see #105
|
||||
|
||||
## v0.1.101 (2024-07-07)
|
||||
|
||||
### Fix
|
||||
|
||||
- #81 修复播放列表时,当前歌曲不在列表没有更换歌曲的问题
|
||||
- #110 修复配置加载问题
|
||||
|
||||
## v0.1.100 (2024-07-07)
|
||||
|
||||
### Fix
|
||||
|
||||
- 日志代码写错
|
||||
|
||||
## v0.1.99 (2024-07-07)
|
||||
|
||||
### Fix
|
||||
|
||||
- #81 修复播放列表没有继续播放上次播放的歌曲,并把随机播放,全部循环,单曲循环状态落地
|
||||
|
||||
## v0.1.98 (2024-07-07)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复多设备获取不到对话记录的问题 see #65
|
||||
- #93 修复目录深度设置后导致目录下的歌曲无法加到播放列表里的问题
|
||||
|
||||
## v0.1.97 (2024-07-06)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复网页控制台设置页面保存报错
|
||||
|
||||
## v0.1.96 (2024-07-06)
|
||||
|
||||
### Feat
|
||||
|
||||
@@ -39,9 +39,9 @@ docker run -p 8090:8090 \
|
||||
hanxi/xiaomusic
|
||||
```
|
||||
|
||||
启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。
|
||||
启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
|
||||
|
||||
### ✨ 修改8090端口
|
||||
### ✨✨✨ 修改默认8090端口映射 ✨✨✨
|
||||
|
||||
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
|
||||
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
## pip 方式安装运行
|
||||
|
||||
```shell
|
||||
> pip install xiaomusic
|
||||
> pip install -U xiaomusic
|
||||
> xiaomusic --help
|
||||
__ __ _ __ __ _
|
||||
\ \/ / (_) __ _ ___ | \/ | _ _ ___ (_) ___
|
||||
@@ -316,7 +316,7 @@ services:
|
||||
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
|
||||
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认情况会把配置存放在music目录,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
|
||||
- XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,见 <https://github.com/hanxi/xiaomusic/issues/82>
|
||||
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,触屏版目前还需要设置这个为 true 。
|
||||
- ~XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,目前应该所有型号都已经兼容,可以不用设置这个~。
|
||||
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
cz bump --check-consistency --increment patch
|
||||
|
||||
#git push -u origin main --tags
|
||||
git push -u origin main --tags
|
||||
|
||||
54
pdm.lock
generated
54
pdm.lock
generated
@@ -4,8 +4,8 @@
|
||||
[metadata]
|
||||
groups = ["default", "lint", "dev"]
|
||||
strategy = ["cross_platform"]
|
||||
lock_version = "4.4.1"
|
||||
content_hash = "sha256:3631f504ea2c9e450ff20fe555e2ec0143bc315c22ff257e17a992d1e6d3c39d"
|
||||
lock_version = "4.4.2"
|
||||
content_hash = "sha256:eb54172c14fe68ae15ebe610f91827d981b225e4b8c44cf1fa7de1521d6f7453"
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@@ -575,7 +575,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "miservice-fork"
|
||||
version = "2.6.1"
|
||||
version = "2.7.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "XiaoMi Cloud Service fork from https://github.com/Yonsm/MiService"
|
||||
dependencies = [
|
||||
@@ -584,8 +584,8 @@ dependencies = [
|
||||
"rich",
|
||||
]
|
||||
files = [
|
||||
{file = "miservice_fork-2.6.1-py3-none-any.whl", hash = "sha256:9b2cc4208486bbbf788d1bde6e2cbc70f241ce10db4dca6f918076a2d2942a39"},
|
||||
{file = "miservice_fork-2.6.1.tar.gz", hash = "sha256:1702281e1e9827958eb3e82bc3242cd013c018e9aa1de8509b4805b5ccf5e60c"},
|
||||
{file = "miservice_fork-2.7.0-py3-none-any.whl", hash = "sha256:8e2c91ebe1fc4b3c63b01cbf1818e5d5833d024fd3a8311970d649ad2d49d6e6"},
|
||||
{file = "miservice_fork-2.7.0.tar.gz", hash = "sha256:8e87ef6d89adceaf3f7a98242cdae1c9135498d77f2c743ec3871d2e42bcbab8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -786,28 +786,28 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
requires_python = ">=3.7"
|
||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
files = [
|
||||
{file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"},
|
||||
{file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"},
|
||||
{file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"},
|
||||
{file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"},
|
||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"},
|
||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"},
|
||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"},
|
||||
{file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"},
|
||||
{file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"},
|
||||
{file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"},
|
||||
{file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"},
|
||||
{file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"},
|
||||
{file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"},
|
||||
{file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"},
|
||||
{file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"},
|
||||
{file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"},
|
||||
{file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"},
|
||||
{file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"},
|
||||
{file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -985,7 +985,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "yt-dlp"
|
||||
version = "2024.7.1.232715.dev0"
|
||||
version = "2024.7.9"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A feature-rich command-line audio/video downloader"
|
||||
dependencies = [
|
||||
@@ -999,8 +999,8 @@ dependencies = [
|
||||
"websockets>=12.0",
|
||||
]
|
||||
files = [
|
||||
{file = "yt_dlp-2024.7.1.232715.dev0-py3-none-any.whl", hash = "sha256:e9ab443353da0c8f01587b031fb84b2cc42eae82aeaa03a9ce5ed6edc301b503"},
|
||||
{file = "yt_dlp-2024.7.1.232715.dev0.tar.gz", hash = "sha256:4f1ab25318c9156cca0b7308bdd2aeb3e7f01e8d9fb83916b4719010038170c8"},
|
||||
{file = "yt_dlp-2024.7.9-py3-none-any.whl", hash = "sha256:b50a595abde523b5cc84d788f97e69c642503bd673ba740f709ebf65b5ec6592"},
|
||||
{file = "yt_dlp-2024.7.9.tar.gz", hash = "sha256:e19f00f9e55e90bca1c94bcaf809aa33e51634be9f0de2df84a72d3206934f94"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[project]
|
||||
name = "xiaomusic"
|
||||
version = "0.1.96"
|
||||
version = "0.2.0"
|
||||
description = "Play Music with xiaomi AI speaker"
|
||||
authors = [
|
||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||
]
|
||||
dependencies = [
|
||||
"aiohttp>=3.8.6",
|
||||
"miservice-fork>=2.5.0",
|
||||
"miservice-fork>=2.7.0",
|
||||
"mutagen>=1.47.0",
|
||||
"yt-dlp>=2024.07.01",
|
||||
"flask[async]>=3.0.1",
|
||||
|
||||
@@ -220,12 +220,12 @@ click==8.1.7 \
|
||||
colorama==0.4.6; platform_system == "Windows" \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
flask==3.0.3 \
|
||||
--hash=sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3 \
|
||||
--hash=sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842
|
||||
flask-HTTPAuth==4.8.0 \
|
||||
--hash=sha256:66568a05bc73942c65f1e2201ae746295816dc009edd84b482c44c758d75097a \
|
||||
--hash=sha256:a58fedd09989b9975448eef04806b096a3964a7feeebc0a78831ff55685b62b0
|
||||
flask[async]==3.0.3 \
|
||||
--hash=sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3 \
|
||||
--hash=sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842
|
||||
frozenlist==1.4.0 \
|
||||
--hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \
|
||||
--hash=sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251 \
|
||||
@@ -305,9 +305,9 @@ MarkupSafe==2.1.4 \
|
||||
mdurl==0.1.2 \
|
||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||
miservice-fork==2.6.1 \
|
||||
--hash=sha256:1702281e1e9827958eb3e82bc3242cd013c018e9aa1de8509b4805b5ccf5e60c \
|
||||
--hash=sha256:9b2cc4208486bbbf788d1bde6e2cbc70f241ce10db4dca6f918076a2d2942a39
|
||||
miservice-fork==2.7.0 \
|
||||
--hash=sha256:8e2c91ebe1fc4b3c63b01cbf1818e5d5833d024fd3a8311970d649ad2d49d6e6 \
|
||||
--hash=sha256:8e87ef6d89adceaf3f7a98242cdae1c9135498d77f2c743ec3871d2e42bcbab8
|
||||
multidict==6.0.4 \
|
||||
--hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \
|
||||
--hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \
|
||||
@@ -472,6 +472,6 @@ yarl==1.9.2 \
|
||||
--hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \
|
||||
--hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \
|
||||
--hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560
|
||||
yt-dlp==2024.7.1.232715.dev0 \
|
||||
--hash=sha256:4f1ab25318c9156cca0b7308bdd2aeb3e7f01e8d9fb83916b4719010038170c8 \
|
||||
--hash=sha256:e9ab443353da0c8f01587b031fb84b2cc42eae82aeaa03a9ce5ed6edc301b503
|
||||
yt-dlp==2024.7.9 \
|
||||
--hash=sha256:b50a595abde523b5cc84d788f97e69c642503bd673ba740f709ebf65b5ec6592 \
|
||||
--hash=sha256:e19f00f9e55e90bca1c94bcaf809aa33e51634be9f0de2df84a72d3206934f94
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.96"
|
||||
__version__ = "0.2.0"
|
||||
|
||||
@@ -73,7 +73,7 @@ class Config:
|
||||
hostname: str = os.getenv("XIAOMUSIC_HOSTNAME", "192.168.2.5")
|
||||
port: int = int(os.getenv("XIAOMUSIC_PORT", "8090")) # 监听端口
|
||||
public_port: int = int(os.getenv("XIAOMUSIC_PUBLIC_PORT", 0)) # 歌曲访问端口
|
||||
proxy: str | None = os.getenv("XIAOMUSIC_PROXY", None)
|
||||
proxy: str = os.getenv("XIAOMUSIC_PROXY", None)
|
||||
search_prefix: str = os.getenv(
|
||||
"XIAOMUSIC_SEARCH", "bilisearch:"
|
||||
) # "bilisearch:" or "ytsearch:"
|
||||
@@ -123,6 +123,7 @@ class Config:
|
||||
enable_force_stop: bool = (
|
||||
os.getenv("XIAOMUSIC_ENABLE_FORCE_STOP", "false").lower() == "true"
|
||||
)
|
||||
play_type: int = int(os.getenv("XIAOMUSIC_PLAY_TYPE", "2"))
|
||||
|
||||
def append_keyword(self, keys, action):
|
||||
for key in keys.split(","):
|
||||
@@ -161,43 +162,39 @@ class Config:
|
||||
config[key] = value
|
||||
return cls(**config)
|
||||
|
||||
@classmethod
|
||||
def convert_value(cls, k, v, type_hints):
|
||||
if v is not None and k in type_hints:
|
||||
expected_type = type_hints[k]
|
||||
try:
|
||||
if expected_type is bool:
|
||||
converted_value = False
|
||||
if str(v).lower() == "true":
|
||||
converted_value = True
|
||||
else:
|
||||
converted_value = expected_type(v)
|
||||
return converted_value
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"Error converting {k}:{v} to {expected_type}: {e}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def read_from_file(cls, config_path: str) -> dict:
|
||||
result = {}
|
||||
with open(config_path, "rb") as f:
|
||||
config = json.load(f)
|
||||
for key, value in config.items():
|
||||
if value is not None and key in cls.__dataclass_fields__:
|
||||
result[key] = value
|
||||
data = json.load(f)
|
||||
type_hints = get_type_hints(cls)
|
||||
|
||||
for k, v in data.items():
|
||||
converted_value = cls.convert_value(k, v, type_hints)
|
||||
if converted_value is not None:
|
||||
result[k] = converted_value
|
||||
return result
|
||||
|
||||
def update_config(self, data):
|
||||
# 获取类型提示
|
||||
type_hints = get_type_hints(self)
|
||||
|
||||
for k, v in data.items():
|
||||
if v and k in type_hints:
|
||||
# 获取字段的类型
|
||||
expected_type = type_hints[k]
|
||||
|
||||
# 根据期望的类型进行转换
|
||||
if isinstance(v, expected_type):
|
||||
# 如果v已经是正确的类型,则直接赋值
|
||||
setattr(self, k, v)
|
||||
else:
|
||||
# 尝试转换类型
|
||||
try:
|
||||
# 特殊情况处理(例如对布尔值的转换)
|
||||
if expected_type is bool:
|
||||
converted_value = False
|
||||
if v and v.lower() == "true":
|
||||
converted_value = True
|
||||
else:
|
||||
# 使用期望类型的构造函数进行转换
|
||||
converted_value = expected_type(v)
|
||||
except (ValueError, TypeError) as e:
|
||||
print(f"Error converting {v} to {expected_type}: {e}")
|
||||
continue
|
||||
|
||||
# 设置转换后的值
|
||||
setattr(self, k, converted_value)
|
||||
converted_value = self.convert_value(k, v, type_hints)
|
||||
if converted_value is not None:
|
||||
setattr(self, k, converted_value)
|
||||
|
||||
@@ -101,63 +101,48 @@ def custom_sort_key(s):
|
||||
return (2, s)
|
||||
|
||||
|
||||
# fork from https://gist.github.com/dougthor42/001355248518bc64d2f8
|
||||
def walk_to_depth(root, depth=None, *args, **kwargs):
|
||||
"""
|
||||
Wrapper around os.walk that stops after going down `depth` folders.
|
||||
I had my own version, but it wasn't as efficient as
|
||||
http://stackoverflow.com/a/234329/1354930, so I modified to be more
|
||||
similar to nosklo's answer.
|
||||
However, nosklo's answer doesn't work if topdown=False, so I kept my
|
||||
version.
|
||||
"""
|
||||
# Let people use this as a standard `os.walk` function.
|
||||
if depth is None:
|
||||
return os.walk(root, *args, **kwargs)
|
||||
def _get_depth_path(root, directory, depth):
|
||||
# 计算当前目录的深度
|
||||
relative_path = root[len(directory) :].strip(os.sep)
|
||||
path_parts = relative_path.split(os.sep)
|
||||
if len(path_parts) >= depth:
|
||||
return os.path.join(directory, *path_parts[:depth])
|
||||
else:
|
||||
return root
|
||||
|
||||
# remove any trailing separators so that our counts are correct.
|
||||
root = root.rstrip(os.path.sep)
|
||||
|
||||
def main_func(root, depth, *args, **kwargs):
|
||||
"""Faster because it skips traversing dirs that are too deep."""
|
||||
root_depth = root.count(os.path.sep)
|
||||
for dirpath, dirnames, filenames in os.walk(root, *args, **kwargs):
|
||||
yield (dirpath, dirnames, filenames)
|
||||
def _append_files_result(result, root, joinpath, files, support_extension):
|
||||
dir_name = os.path.basename(root)
|
||||
if dir_name not in result:
|
||||
result[dir_name] = []
|
||||
for file in files:
|
||||
# 过滤隐藏文件
|
||||
if file.startswith("."):
|
||||
continue
|
||||
# 过滤文件后缀
|
||||
(name, extension) = os.path.splitext(file)
|
||||
if extension.lower() not in support_extension:
|
||||
continue
|
||||
|
||||
# calculate how far down we are.
|
||||
current_folder_depth = dirpath.count(os.path.sep)
|
||||
if current_folder_depth >= root_depth + depth:
|
||||
del dirnames[:]
|
||||
result[dir_name].append(os.path.join(joinpath, file))
|
||||
|
||||
def fallback_func(root, depth, *args, **kwargs):
|
||||
"""Slower, but works when topdown is False"""
|
||||
root_depth = root.count(os.path.sep)
|
||||
for dirpath, dirnames, filenames in os.walk(root, *args, **kwargs):
|
||||
current_folder_depth = dirpath.count(os.path.sep)
|
||||
if current_folder_depth <= root_depth + depth:
|
||||
yield (dirpath, dirnames, filenames)
|
||||
|
||||
# there's gotta be a better way do do this...
|
||||
try:
|
||||
if args[0] is False:
|
||||
yield from fallback_func(root, depth, *args, **kwargs)
|
||||
return
|
||||
def traverse_music_directory(
|
||||
directory, depth=10, exclude_dirs=None, support_extension=None
|
||||
):
|
||||
result = {}
|
||||
for root, dirs, files in os.walk(directory):
|
||||
# 忽略排除的目录
|
||||
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
||||
|
||||
# 计算当前目录的深度
|
||||
current_depth = root[len(directory) :].count(os.sep) + 1
|
||||
if current_depth > depth:
|
||||
depth_path = _get_depth_path(root, directory, depth - 1)
|
||||
_append_files_result(result, depth_path, root, files, support_extension)
|
||||
else:
|
||||
yield from main_func(root, depth, *args, **kwargs)
|
||||
return
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if kwargs["topdown"] is False:
|
||||
yield from fallback_func(root, depth, *args, **kwargs)
|
||||
return
|
||||
else:
|
||||
yield from main_func(root, depth, *args, **kwargs)
|
||||
return
|
||||
except KeyError:
|
||||
yield from main_func(root, depth, *args, **kwargs)
|
||||
return
|
||||
_append_files_result(result, root, root, files, support_extension)
|
||||
return result
|
||||
|
||||
|
||||
def downloadfile(url):
|
||||
|
||||
@@ -9,6 +9,7 @@ import re
|
||||
import time
|
||||
import traceback
|
||||
import urllib.parse
|
||||
from dataclasses import asdict
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
@@ -37,7 +38,7 @@ from xiaomusic.utils import (
|
||||
get_local_music_duration,
|
||||
get_web_music_duration,
|
||||
parse_cookie_string,
|
||||
walk_to_depth,
|
||||
traverse_music_directory,
|
||||
)
|
||||
|
||||
EOF = object()
|
||||
@@ -64,8 +65,6 @@ class XiaoMusic:
|
||||
|
||||
# 下载对象
|
||||
self.download_proc = None
|
||||
# 单曲循环,全部循环
|
||||
self.play_type = PLAY_TYPE_RND
|
||||
self.cur_music = ""
|
||||
self._next_timer = None
|
||||
self._timeout = 0
|
||||
@@ -102,6 +101,8 @@ class XiaoMusic:
|
||||
self.log.info(f"Startup OK. {debug_config}")
|
||||
|
||||
def init_config(self):
|
||||
# 单曲循环,全部循环
|
||||
self.play_type = self.config.play_type
|
||||
self.music_path = self.config.music_path
|
||||
self.conf_path = self.config.conf_path
|
||||
if not self.conf_path:
|
||||
@@ -198,12 +199,12 @@ class XiaoMusic:
|
||||
self.device2hardware = {}
|
||||
self.did2device = {}
|
||||
for h in hardware_data:
|
||||
device = h.get("deviceID", "")
|
||||
device_id = h.get("deviceID", "")
|
||||
hardware = h.get("hardware", "")
|
||||
did = h.get("miotDID", "")
|
||||
if device and hardware and did and (did in mi_dids):
|
||||
self.device2hardware[device] = hardware
|
||||
self.did2device[did] = device
|
||||
if device_id and hardware and did and (did in mi_dids):
|
||||
self.device2hardware[device_id] = hardware
|
||||
self.did2device[did] = device_id
|
||||
except Exception as e:
|
||||
self.log.error(f"Execption {e}")
|
||||
|
||||
@@ -236,8 +237,9 @@ class XiaoMusic:
|
||||
for i in range(retries):
|
||||
try:
|
||||
timeout = ClientTimeout(total=15)
|
||||
hardware = self.device2hardware[device_id]
|
||||
url = LATEST_ASK_API.format(
|
||||
hardware=self.config.hardware,
|
||||
hardware=hardware,
|
||||
timestamp=str(int(time.time() * 1000)),
|
||||
)
|
||||
self.log.debug(f"url:{url}")
|
||||
@@ -481,39 +483,34 @@ class XiaoMusic:
|
||||
encoded_name = urllib.parse.quote(filename)
|
||||
return f"http://{self.hostname}:{self.public_port}/{encoded_name}"
|
||||
|
||||
# 递归获取目录下所有歌曲,生成随机播放列表
|
||||
# 获取目录下所有歌曲,生成随机播放列表
|
||||
def _gen_all_music_list(self):
|
||||
self._all_music = {}
|
||||
all_music_by_dir = {}
|
||||
for root, dirs, filenames in walk_to_depth(
|
||||
self.music_path, depth=self.music_path_depth
|
||||
):
|
||||
dirs[:] = [d for d in dirs if d not in self.exclude_dirs]
|
||||
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:
|
||||
local_musics = traverse_music_directory(
|
||||
self.music_path,
|
||||
depth=self.music_path_depth,
|
||||
exclude_dirs=self.exclude_dirs,
|
||||
support_extension=SUPPORT_MUSIC_TYPE,
|
||||
)
|
||||
for dir_name, files in local_musics.items():
|
||||
if len(files) == 0:
|
||||
continue
|
||||
if dir_name == os.path.basename(self.music_path):
|
||||
dir_name = "其他"
|
||||
if self.music_path != self.download_path and dir_name == os.path.basename(
|
||||
self.download_path
|
||||
):
|
||||
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)
|
||||
# 过滤隐藏文件
|
||||
if filename.startswith("."):
|
||||
continue
|
||||
# 过滤非音乐文件
|
||||
(name, extension) = os.path.splitext(filename)
|
||||
self.log.debug(
|
||||
"gen_all_music_list. filename:%s, name:%s, extension:%s",
|
||||
filename,
|
||||
name,
|
||||
extension,
|
||||
)
|
||||
if extension.lower() not in SUPPORT_MUSIC_TYPE:
|
||||
continue
|
||||
|
||||
for file in files:
|
||||
# 歌曲名字相同会覆盖
|
||||
self._all_music[name] = os.path.join(root, filename)
|
||||
filename = os.path.basename(file)
|
||||
(name, _) = os.path.splitext(filename)
|
||||
self._all_music[name] = file
|
||||
all_music_by_dir[dir_name][name] = True
|
||||
|
||||
self._play_list = list(self._all_music.keys())
|
||||
self._cur_play_list = "全部"
|
||||
self._gen_play_list()
|
||||
@@ -715,10 +712,17 @@ class XiaoMusic:
|
||||
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
||||
oparg = argpre
|
||||
opvalue = self.config.key_word_dict.get(opkey)
|
||||
if not ctrl_panel and not self.isplaying():
|
||||
if self.active_cmd and opvalue not in self.active_cmd:
|
||||
self.log.ifno(f"不在激活命令中 {opvalue}")
|
||||
continue
|
||||
|
||||
if (
|
||||
(not ctrl_panel)
|
||||
and (not self.isplaying())
|
||||
and self.active_cmd
|
||||
and (opvalue not in self.active_cmd)
|
||||
and (opkey not in self.active_cmd)
|
||||
):
|
||||
self.log.info(f"不在激活命令中 {opvalue}")
|
||||
continue
|
||||
|
||||
self.log.info(f"匹配到指令. opkey:{opkey} opvalue:{opvalue} oparg:{oparg}")
|
||||
return (opvalue, oparg)
|
||||
self.log.info(f"未匹配到指令 {query} {ctrl_panel}")
|
||||
@@ -726,6 +730,10 @@ class XiaoMusic:
|
||||
|
||||
# 判断是否播放下一首歌曲
|
||||
def check_play_next(self):
|
||||
# 当前歌曲不在当前播放列表
|
||||
if self.cur_music not in self._play_list:
|
||||
return True
|
||||
|
||||
# 当前没我在播放的歌曲
|
||||
if self.cur_music == "":
|
||||
return True
|
||||
@@ -742,7 +750,7 @@ class XiaoMusic:
|
||||
async def play_one_url(self, device_id, url):
|
||||
try:
|
||||
if self.config.use_music_api:
|
||||
ret = await self.play_by_music_url(device_id, url)
|
||||
ret = await self.mina_service.play_by_music_url(device_id, url)
|
||||
self.log.info(
|
||||
f"play_one_url play_by_music_url device_id:{device_id} ret:{ret} url:{url}"
|
||||
)
|
||||
@@ -841,7 +849,7 @@ class XiaoMusic:
|
||||
|
||||
# 下一首
|
||||
async def play_next(self, **kwargs):
|
||||
self.log.info("下一首")
|
||||
self.log.info("开始播放下一首")
|
||||
name = self.cur_music
|
||||
self.log.debug("play_next. name:%s, cur_music:%s", name, self.cur_music)
|
||||
if (
|
||||
@@ -858,18 +866,21 @@ class XiaoMusic:
|
||||
# 单曲循环
|
||||
async def set_play_type_one(self, **kwargs):
|
||||
self.play_type = PLAY_TYPE_ONE
|
||||
self.save_play_type()
|
||||
await self.do_tts("已经设置为单曲循环")
|
||||
|
||||
# 全部循环
|
||||
async def set_play_type_all(self, **kwargs):
|
||||
self.play_type = PLAY_TYPE_ALL
|
||||
self._gen_play_list()
|
||||
self.save_play_type()
|
||||
await self.do_tts("已经设置为全部循环")
|
||||
|
||||
# 随机播放
|
||||
async def random_play(self, **kwargs):
|
||||
self.play_type = PLAY_TYPE_RND
|
||||
self._gen_play_list()
|
||||
self.save_play_type()
|
||||
await self.do_tts("已经设置为随机播放")
|
||||
|
||||
# 刷新列表
|
||||
@@ -901,7 +912,8 @@ class XiaoMusic:
|
||||
if real_name:
|
||||
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}】")
|
||||
list_name = real_name
|
||||
self.log.info(f"没找到播放列表【{list_name}】")
|
||||
else:
|
||||
self.log.info(f"没找到播放列表【{list_name}】")
|
||||
return list_name
|
||||
|
||||
# 播放一个播放列表
|
||||
@@ -921,8 +933,6 @@ class XiaoMusic:
|
||||
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):
|
||||
@@ -1018,12 +1028,30 @@ class XiaoMusic:
|
||||
|
||||
# 保存配置并重新启动
|
||||
async def saveconfig(self, data):
|
||||
# 配置文件落地
|
||||
self.do_saveconfig(data)
|
||||
# 更新配置
|
||||
self.update_config_from_setting(data)
|
||||
# 重新初始化
|
||||
await self.call_main_thread_function(self.reinit)
|
||||
|
||||
# 配置文件落地
|
||||
def do_saveconfig(self, data):
|
||||
# 默认暂时配置保存到 music 目录下
|
||||
filename = self.getsettingfile()
|
||||
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 save_cur_config(self):
|
||||
data = asdict(self.config)
|
||||
self.do_saveconfig(data)
|
||||
self.log.info("save_cur_config ok")
|
||||
|
||||
# 播放类型落地
|
||||
def save_play_type(self):
|
||||
self.config.play_type = self.play_type
|
||||
self.save_cur_config()
|
||||
|
||||
def update_config_from_setting(self, data):
|
||||
# 兼容旧配置:一段时间后清理这里的旧代码
|
||||
@@ -1051,14 +1079,17 @@ class XiaoMusic:
|
||||
async def getalldevices(self, **kwargs):
|
||||
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)
|
||||
try:
|
||||
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)
|
||||
except Exception as e:
|
||||
self.log.error(f"Execption {e}")
|
||||
alldevices = {
|
||||
"did_list": did_list,
|
||||
"hardware_list": hardware_list,
|
||||
@@ -1081,49 +1112,6 @@ class XiaoMusic:
|
||||
result = await future
|
||||
return result
|
||||
|
||||
async def play_by_music_url(self, deviceId, url, _type=2):
|
||||
self.log.info(f"play_by_music_url url:{url}, type:{_type}")
|
||||
audio_type = ""
|
||||
if _type == 1:
|
||||
# If set to MUSIC, the light will be on
|
||||
audio_type = "MUSIC"
|
||||
audio_id = self.config.use_music_audio_id
|
||||
id = self.config.use_music_id
|
||||
music = {
|
||||
"payload": {
|
||||
"audio_type": audio_type,
|
||||
"audio_items": [
|
||||
{
|
||||
"item_id": {
|
||||
"audio_id": audio_id,
|
||||
"cp": {
|
||||
"album_id": "-1",
|
||||
"episode_index": 0,
|
||||
"id": id,
|
||||
"name": "xiaowei",
|
||||
},
|
||||
},
|
||||
"stream": {"url": url},
|
||||
}
|
||||
],
|
||||
"list_params": {
|
||||
"listId": "-1",
|
||||
"loadmore_offset": 0,
|
||||
"origin": "xiaowei",
|
||||
"type": "MUSIC",
|
||||
},
|
||||
},
|
||||
"play_behavior": "REPLACE_ALL",
|
||||
}
|
||||
data = {"startaudioid": audio_id, "music": json.dumps(music)}
|
||||
self.log.info(json.dumps(data))
|
||||
return await self.mina_service.ubus_request(
|
||||
deviceId,
|
||||
"player_play_music",
|
||||
"mediaplayer",
|
||||
data,
|
||||
)
|
||||
|
||||
async def debug_play_by_music_url(self, arg1=None):
|
||||
if arg1 is None:
|
||||
arg1 = {}
|
||||
|
||||
Reference in New Issue
Block a user