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

Compare commits

...

20 Commits

Author SHA1 Message Date
涵曦
aa698667c9 bump: version 0.1.101 → 0.2.0 2024-07-09 15:12:14 +00:00
涵曦
700e17854c Update README.md 2024-07-09 23:11:18 +08:00
涵曦
40258c9fa1 feat: 触屏版可以不用设置 XIAOMUSIC_USE_MUSIC_API 2024-07-09 15:07:46 +00:00
涵曦
eb59bf0db5 feat: 升级依赖库 2024-07-09 15:03:20 +00:00
涵曦
a9df78af97 Update README.md 2024-07-08 18:00:02 +08:00
涵曦
901506a32d feat: 唤醒口令配置支持配语音词,简化自定义口令配置 see #105 2024-07-08 01:06:01 +00:00
涵曦
0ddbe58fbd bump: version 0.1.100 → 0.1.101 2024-07-07 08:47:33 +00:00
涵曦
350d82184f fix: #81 修复播放列表时,当前歌曲不在列表没有更换歌曲的问题 2024-07-07 08:43:55 +00:00
涵曦
5b8054abd9 fix: #110 修复配置加载问题 2024-07-07 08:28:09 +00:00
涵曦
c5c691b653 bump: version 0.1.99 → 0.1.100 2024-07-07 06:59:44 +00:00
涵曦
7a44c8587c fix: 日志代码写错 2024-07-07 06:41:45 +00:00
涵曦
5092ffc91a bump: version 0.1.98 → 0.1.99 2024-07-07 06:39:08 +00:00
涵曦
2da12e12d5 fix: #81 修复播放列表没有继续播放上次播放的歌曲,并把随机播放,全部循环,单曲循环状态落地 2024-07-07 06:21:15 +00:00
涵曦
5aff72dbb6 bump: version 0.1.97 → 0.1.98 2024-07-07 05:48:40 +00:00
涵曦
043f452e71 Update README.md 2024-07-07 13:47:30 +08:00
涵曦
5cedf8a907 fix: 修复多设备获取不到对话记录的问题 see #65 2024-07-07 04:46:33 +00:00
涵曦
ae77c7232e Update README.md 2024-07-07 11:42:05 +08:00
涵曦
d559413d46 fix: #93 修复目录深度设置后导致目录下的歌曲无法加到播放列表里的问题 2024-07-07 02:03:17 +00:00
涵曦
8b185d8768 bump: version 0.1.96 → 0.1.97 2024-07-06 16:05:28 +00:00
涵曦
202105a11f fix: 修复网页控制台设置页面保存报错 2024-07-06 16:04:07 +00:00
10 changed files with 231 additions and 221 deletions

View File

@@ -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

View File

@@ -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 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。

View File

@@ -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
View File

@@ -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]]

View File

@@ -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",

View File

@@ -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

View File

@@ -1 +1 @@
__version__ = "0.1.96"
__version__ = "0.2.0"

View File

@@ -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)

View File

@@ -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):

View File

@@ -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 = {}