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

Compare commits

...

78 Commits

Author SHA1 Message Date
涵曦
4de032e193 bump: version 0.3.19 → 0.3.20 2024-07-30 06:09:27 +00:00
涵曦
d655157e1d build: update static version 2024-07-30 06:09:26 +00:00
涵曦
ff06958ab3 fix: 尝试修复延迟问题,修复播放停止不了的问题 2024-07-30 06:09:18 +00:00
涵曦
6e98b5ee2b bump: version 0.3.18 → 0.3.19 2024-07-30 01:59:32 +00:00
涵曦
da90fe2633 build: update static version 2024-07-30 01:59:31 +00:00
涵曦
eaedef452c fix: 调整配置,优化获取歌曲时长接口 2024-07-30 01:09:01 +00:00
涵曦
2d5f3799a3 bump: version 0.3.17 → 0.3.18 2024-07-29 10:16:10 +00:00
涵曦
b52bfe0848 build: update static version 2024-07-29 10:16:09 +00:00
涵曦
e2261b2d19 fix: #135 修复获取不到播放时长时只播放3秒的问题 2024-07-29 10:15:49 +00:00
涵曦
2443444165 Update release.yml 2024-07-29 05:51:04 +08:00
涵曦
7c912a51be bump: version 0.3.16 → 0.3.17 2024-07-28 21:42:49 +00:00
涵曦
bda55a1faa build: update static version 2024-07-28 21:42:48 +00:00
涵曦
5b5f957f8e fix: 优化日志输出,尝试排查延迟播放的问题 2024-07-28 21:42:23 +00:00
涵曦
12f54e3ad4 Update release.yml 2024-07-29 04:48:39 +08:00
quxiaozha
d6594e1270 Fix docker run command 2024-07-29 04:42:02 +08:00
涵曦
b678447417 bump: version 0.3.15 → 0.3.16 2024-07-28 13:58:52 +00:00
涵曦
02508f6997 build: update static version 2024-07-28 13:58:52 +00:00
涵曦
d50fff9e31 build: 修复版本打包问题 2024-07-28 13:58:44 +00:00
涵曦
2d7d7ddc95 bump: version 0.3.14 → 0.3.15 2024-07-28 13:07:36 +00:00
涵曦
9c9825d423 build: update static version 2024-07-28 13:07:35 +00:00
涵曦
f788c0f37b fix: 修复自定义口令重复的问题 2024-07-28 13:07:14 +00:00
涵曦
36d72d1eca fix: 修复日志输出问题 2024-07-28 12:59:32 +00:00
涵曦
6b17779c59 Update release.yml 2024-07-28 17:33:43 +08:00
涵曦
759130e38d fix: 修复退出异常问题 2024-07-28 08:48:39 +00:00
涵曦
49f727477e style: 优化日志输出,方便排查问题 2024-07-28 08:09:34 +00:00
涵曦
43886116c1 bump: version 0.3.13 → 0.3.14 2024-07-28 02:02:09 +00:00
涵曦
a38194027d build: update static version 2024-07-28 02:02:08 +00:00
涵曦
eab4f4bd46 feat: 优化播放延迟问题,并新增配置下一首播放的延迟秒数 2024-07-28 00:51:20 +00:00
涵曦
6b38676766 build: 打包优化 2024-07-26 14:21:43 +00:00
涵曦
3830f58c0b bump: version 0.3.12 → 0.3.13 2024-07-24 23:07:11 +00:00
涵曦
34cdea1731 build: update static version 2024-07-24 23:07:10 +00:00
涵曦
22f545b99c fix: 解决 docker 镜像问题 2024-07-24 21:58:28 +00:00
涵曦
37f73dc31f bump: version 0.3.11 → 0.3.12 2024-07-24 13:34:37 +00:00
涵曦
10e52f0b63 build: update static version 2024-07-24 13:34:36 +00:00
涵曦
b0ac1034d2 feat: 优化获取文件播放时长接口,尝试解决播放延迟和操作面板卡顿的问题 2024-07-24 01:47:46 +00:00
涵曦
48d663a764 bump: version 0.3.10 → 0.3.11 2024-07-22 15:30:55 +00:00
涵曦
08ab75b390 build: update static version 2024-07-22 15:30:54 +00:00
涵曦
71dfc6d468 fix: #130 单曲循环的模式下,播放列表的指令不生效 2024-07-22 15:30:30 +00:00
涵曦
7877775495 refactor: 优化代码 2024-07-19 14:25:56 +00:00
涵曦
7744a75773 build: 更新依赖库 2024-07-19 14:14:49 +00:00
bowji
a28a0267e4 feat: Add remove mp3 id3 tag function
Signed-off-by: bowji <bowji@bowji-m-dnpx.dclife.fun>

Signed-off-by: Bowen Ji <jibwf@hotmail.com>
2024-07-19 14:09:05 +00:00
涵曦
417a5c924a bump: version 0.3.9 → 0.3.10 2024-07-19 12:18:50 +00:00
涵曦
d4bc1c49ea build: update static version 2024-07-19 12:18:49 +00:00
涵曦
51ef9e08fe fix: 修复软连接目录不能播放的问题 2024-07-19 06:10:14 +00:00
涵曦
285203a342 style: 优化日志输出 2024-07-18 09:54:31 +00:00
涵曦
af6077693e fix: 修复自定义语音口令设置不生效的问题 2024-07-18 01:52:04 +00:00
涵曦
40ac67cce0 Update FUNDING.yml 2024-07-18 00:15:38 +08:00
涵曦
96d03c5c29 feat: 支持软连接的接口直接用os.walk即可 2024-07-17 14:58:03 +00:00
涵曦
82aa453e50 bump: version 0.3.8 → 0.3.9 2024-07-17 10:59:13 +00:00
涵曦
1718211619 build: update static version 2024-07-17 10:59:13 +00:00
涵曦
09310675fc feat: #119 音乐目录支持软连接 2024-07-17 09:57:24 +00:00
涵曦
ca711bbdb8 fix: 修复日志下载报错问题 2024-07-17 06:05:15 +00:00
涵曦
fb44f88df2 fix: 兼容旧的setting.json文件中conf_path为空的情况 2024-07-17 06:05:15 +00:00
涵曦
e5b32b2831 fix: 修复设置页面可能打不开的问题 2024-07-17 06:05:15 +00:00
涵曦
19ddbb7ca9 Update README.md 2024-07-17 00:32:42 +08:00
涵曦
3e82d7acdc build: 默认映射目录设置 2024-07-16 16:25:01 +00:00
涵曦
3921c70c86 bump: version 0.3.7 → 0.3.8 2024-07-16 13:58:04 +00:00
涵曦
aea9333e57 build: update static version 2024-07-16 13:58:03 +00:00
涵曦
7043ca31cf fix: 修复播放url接口问题 2024-07-16 13:57:37 +00:00
涵曦
963d86de7c bump: version 0.3.6 → 0.3.7 2024-07-16 09:38:51 +00:00
涵曦
d6b0e974b7 build: update static version 2024-07-16 09:38:50 +00:00
涵曦
8a8340a159 feat: 播放链接按钮对应给个默认的链接用于测试 2024-07-16 09:38:36 +00:00
涵曦
20d3c9fce9 feat: Uvicorn 的日志信息合并到 xiaomusic 日志里显示 2024-07-16 05:01:50 +00:00
涵曦
729549a7a9 bump: version 0.3.5 → 0.3.6 2024-07-15 17:22:30 +00:00
涵曦
d28614177c build: update static version 2024-07-15 17:22:30 +00:00
涵曦
db53517784 fix: #126 修复pip安装时主页打不开的问题 2024-07-15 17:22:24 +00:00
涵曦
186e9c1417 bump: version 0.3.4 → 0.3.5 2024-07-15 17:17:26 +00:00
涵曦
7498016d61 build: update static version 2024-07-15 17:17:26 +00:00
涵曦
7114ea2e6e fix: #116 播放失败自动切下首歌 2024-07-15 17:17:08 +00:00
涵曦
e8ca1f8678 bump: version 0.3.3 → 0.3.4 2024-07-15 16:46:06 +00:00
涵曦
2275bc2600 build: update static version 2024-07-15 16:46:06 +00:00
涵曦
45a94f4bfe fix: #125 修复本地英文歌曲匹大小写字母配不到的问题 2024-07-15 16:45:27 +00:00
涵曦
5cb2c84715 build: update code 2024-07-15 15:40:16 +00:00
涵曦
a3bf8d8aaa build: 静态文件版本自动更新脚本修改 2024-07-15 15:37:45 +00:00
涵曦
0a88b7be26 build: update static version 2024-07-15 15:24:26 +00:00
涵曦
05da3e8fc4 bump: version 0.3.2 → 0.3.3 2024-07-15 15:19:40 +00:00
涵曦
560ea1aeca fix: 尝试修复播放卡顿问题 see #124 2024-07-15 15:16:38 +00:00
涵曦
5ef1f2d940 ci: 发版本自动更新静态文件版本号 2024-07-15 14:53:25 +00:00
24 changed files with 956 additions and 1518 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,2 @@
github: [hanxi]
custom: ['https://afdian.net/a/imhanxi']
custom: ['https://afdian.com/a/imhanxi']

View File

@@ -10,31 +10,25 @@ on:
workflow_dispatch:
jobs:
release-pypi:
name: Build and Release PyPI
pypi-publish:
name: upload release to PyPI
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: "3.10"
- uses: pdm-project/setup-pdm@v3
- name: Publish package distributions to PyPI
run: pdm publish
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Build artifacts
run: |
pip install build
python -m build
- uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
node-version: 16.x
- run: npx changelogithub
continue-on-error: true

2
.gitignore vendored
View File

@@ -165,3 +165,5 @@ ffmpeg
music
test.sh
conf
setting.json
.DS_Store

View File

@@ -1,3 +1,129 @@
## v0.3.20 (2024-07-30)
### Fix
- 尝试修复延迟问题,修复播放停止不了的问题
## v0.3.19 (2024-07-30)
### Fix
- 调整配置,优化获取歌曲时长接口
## v0.3.18 (2024-07-29)
### Fix
- #135 修复获取不到播放时长时只播放3秒的问题
## v0.3.17 (2024-07-28)
### Fix
- 优化日志输出,尝试排查延迟播放的问题
## v0.3.16 (2024-07-28)
## v0.3.15 (2024-07-28)
### Fix
- 修复自定义口令重复的问题
- 修复日志输出问题
- 修复退出异常问题
## v0.3.14 (2024-07-28)
### Feat
- 优化播放延迟问题,并新增配置下一首播放的延迟秒数
## v0.3.13 (2024-07-24)
### Fix
- 解决 docker 镜像问题
## v0.3.12 (2024-07-24)
### Feat
- 优化获取文件播放时长接口,尝试解决播放延迟和操作面板卡顿的问题
## v0.3.11 (2024-07-22)
### Feat
- Add remove mp3 id3 tag function
### Fix
- #130 单曲循环的模式下,播放列表的指令不生效
### Refactor
- 优化代码
## v0.3.10 (2024-07-19)
### Feat
- 支持软连接的接口直接用os.walk即可
### Fix
- 修复软连接目录不能播放的问题
- 修复自定义语音口令设置不生效的问题
## v0.3.9 (2024-07-17)
### Feat
- #119 音乐目录支持软连接
### Fix
- 修复日志下载报错问题
- 兼容旧的setting.json文件中conf_path为空的情况
- 修复设置页面可能打不开的问题
## v0.3.8 (2024-07-16)
### Fix
- 修复播放url接口问题
## v0.3.7 (2024-07-16)
### Feat
- 播放链接按钮对应给个默认的链接用于测试
- Uvicorn 的日志信息合并到 xiaomusic 日志里显示
## v0.3.6 (2024-07-15)
### Fix
- #126 修复pip安装时主页打不开的问题
## v0.3.5 (2024-07-15)
### Fix
- #116 播放失败自动切下首歌
## v0.3.4 (2024-07-15)
### Fix
- #125 修复本地英文歌曲匹大小写字母配不到的问题
## v0.3.3 (2024-07-15)
### Fix
- 尝试修复播放卡顿问题 see #124
## v0.3.2 (2024-07-15)
### Fix

View File

@@ -1,8 +1,13 @@
FROM python:3.10 AS builder
ENV DEBIAN_FRONTEND=noninteractive
RUN pip install -U pdm
ENV PDM_CHECK_UPDATE=false
WORKDIR /app
COPY requirements.txt .
RUN python3 -m venv .venv && .venv/bin/pip install --upgrade pip && .venv/bin/pip install --no-cache-dir -r requirements.txt
COPY pyproject.toml README.md .
COPY xiaomusic/ ./xiaomusic/
COPY plugins/ ./plugins/
COPY xiaomusic.py .
RUN pdm install --prod --no-editable
COPY install_dependencies.sh .
RUN bash install_dependencies.sh
@@ -13,10 +18,10 @@ COPY --from=builder /app/ffmpeg /app/ffmpeg
COPY xiaomusic/ ./xiaomusic/
COPY plugins/ ./plugins/
COPY xiaomusic.py .
ENV XDG_CONFIG_HOME=/config
ENV XIAOMUSIC_HOSTNAME=192.168.2.5
ENV XIAOMUSIC_PORT=8090
VOLUME /config
VOLUME /app/conf
VOLUME /app/music
EXPOSE 8090
ENV PATH=/app/.venv/bin:$PATH
ENTRYPOINT [".venv/bin/python3","xiaomusic.py"]

View File

@@ -37,7 +37,7 @@ services:
```yaml
docker run -p 8090:8090 \
-v ./music:/app/music \
-v ./conf:/app/conf
-v ./conf:/app/conf \
hanxi/xiaomusic
```
@@ -152,11 +152,13 @@ pdm run xiaomusic.py
| LX01 | [小爱音箱mini](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx01) |
| L05B | [小爱音箱Play](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05b) |
| L05C | [小米小爱音箱Play 增强版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05c) |
| LX04 X10A X08A | 已经支持的触屏版 |
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>
> 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。
> 目前应该所有设备类型都已经支持播放,有问题随时反馈。
> 其他触屏版不能播放可以设置 XIAOMUSIC_USE_MUSIC_API 为 true 试试。
## 支持音乐格式
@@ -320,7 +322,7 @@ services:
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台具体见 <https://github.com/hanxi/xiaomusic/issues/47>
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认情况会把配置存放在music目录,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认`/app/config` ,具体见 <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 接口播放音乐,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 <https://github.com/hanxi/xiaomusic/issues/30>
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个

View File

@@ -1,5 +1,9 @@
#!/bin/bash
./update-static-version.py
git add xiaomusic/static
git commit -m 'build: update static version'
cz bump --check-consistency --increment patch
git push -u origin main --tags

View File

@@ -1,5 +1,9 @@
#!/bin/bash
./update-static-version.py
git add xiaomusic/static
git commit -m 'build: update static version'
cz bump --check-consistency
git push -u origin main --tags

1016
pdm.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.3.2"
version = "0.3.20"
description = "Play Music with xiaomi AI speaker"
authors = [
{name = "涵曦", email = "im.hanxi@gmail.com"},
@@ -13,6 +13,7 @@ dependencies = [
"uvicorn>=0.30.1",
"fastapi>=0.111.0",
"starlette>=0.37.2",
"aiofiles>=24.1.0",
]
requires-python = ">=3.10"
readme = "README.md"

View File

@@ -1,787 +0,0 @@
# This file is @generated by PDM.
# Please do not edit it manually.
aiohttp==3.9.5 \
--hash=sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c \
--hash=sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf \
--hash=sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81 \
--hash=sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f \
--hash=sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a \
--hash=sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771 \
--hash=sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb \
--hash=sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430 \
--hash=sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233 \
--hash=sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9 \
--hash=sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59 \
--hash=sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888 \
--hash=sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c \
--hash=sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c \
--hash=sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424 \
--hash=sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2 \
--hash=sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb \
--hash=sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a \
--hash=sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10 \
--hash=sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0 \
--hash=sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4 \
--hash=sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3 \
--hash=sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa \
--hash=sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a \
--hash=sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a \
--hash=sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323 \
--hash=sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6 \
--hash=sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832 \
--hash=sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75 \
--hash=sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6 \
--hash=sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d \
--hash=sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72 \
--hash=sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db \
--hash=sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da \
--hash=sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678 \
--hash=sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b \
--hash=sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f \
--hash=sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58 \
--hash=sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342 \
--hash=sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558 \
--hash=sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551 \
--hash=sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595 \
--hash=sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee \
--hash=sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d \
--hash=sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7 \
--hash=sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f
aiosignal==1.3.1 \
--hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \
--hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17
annotated-types==0.7.0 \
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
anyio==4.4.0 \
--hash=sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94 \
--hash=sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7
async-timeout==4.0.3; python_version < "3.11" \
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
attrs==23.1.0 \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
brotli==1.1.0; implementation_name == "cpython" \
--hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \
--hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \
--hash=sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3 \
--hash=sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd \
--hash=sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409 \
--hash=sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da \
--hash=sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50 \
--hash=sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180 \
--hash=sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d \
--hash=sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc \
--hash=sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265 \
--hash=sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327 \
--hash=sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd \
--hash=sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0 \
--hash=sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0 \
--hash=sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451 \
--hash=sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e \
--hash=sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248 \
--hash=sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91 \
--hash=sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724 \
--hash=sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966 \
--hash=sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951 \
--hash=sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8 \
--hash=sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d \
--hash=sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc \
--hash=sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61 \
--hash=sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1 \
--hash=sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2 \
--hash=sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6 \
--hash=sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9 \
--hash=sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2 \
--hash=sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf \
--hash=sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408 \
--hash=sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752 \
--hash=sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80 \
--hash=sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0 \
--hash=sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e
brotlicffi==1.1.0.0; implementation_name != "cpython" \
--hash=sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b \
--hash=sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171 \
--hash=sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb \
--hash=sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979 \
--hash=sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33 \
--hash=sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca \
--hash=sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f \
--hash=sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6 \
--hash=sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca \
--hash=sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112 \
--hash=sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391 \
--hash=sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8 \
--hash=sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0 \
--hash=sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35 \
--hash=sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820 \
--hash=sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838 \
--hash=sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613 \
--hash=sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5 \
--hash=sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851 \
--hash=sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814 \
--hash=sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc \
--hash=sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13 \
--hash=sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990 \
--hash=sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6 \
--hash=sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d \
--hash=sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808 \
--hash=sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14
certifi==2023.7.22 \
--hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
--hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
cffi==1.16.0; implementation_name != "cpython" \
--hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
--hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \
--hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \
--hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \
--hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \
--hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
--hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \
--hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \
--hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \
--hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \
--hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \
--hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
--hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \
--hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \
--hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
--hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \
--hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
--hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \
--hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
--hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \
--hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \
--hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \
--hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \
--hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \
--hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \
--hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357
charset-normalizer==3.3.0 \
--hash=sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786 \
--hash=sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e \
--hash=sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8 \
--hash=sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa \
--hash=sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d \
--hash=sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382 \
--hash=sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678 \
--hash=sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b \
--hash=sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e \
--hash=sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596 \
--hash=sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69 \
--hash=sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c \
--hash=sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459 \
--hash=sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7 \
--hash=sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908 \
--hash=sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a \
--hash=sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8 \
--hash=sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d \
--hash=sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d \
--hash=sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34 \
--hash=sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6 \
--hash=sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e \
--hash=sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c \
--hash=sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078 \
--hash=sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4 \
--hash=sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403 \
--hash=sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0 \
--hash=sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9 \
--hash=sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05 \
--hash=sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec \
--hash=sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56 \
--hash=sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e \
--hash=sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455 \
--hash=sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65 \
--hash=sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78 \
--hash=sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df \
--hash=sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1 \
--hash=sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989 \
--hash=sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63 \
--hash=sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649 \
--hash=sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2 \
--hash=sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd \
--hash=sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5 \
--hash=sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe \
--hash=sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293 \
--hash=sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e \
--hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e
click==8.1.7 \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
colorama==0.4.6; platform_system == "Windows" or sys_platform == "win32" \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
dnspython==2.6.1 \
--hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \
--hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc
email-validator==2.2.0 \
--hash=sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631 \
--hash=sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7
exceptiongroup==1.2.1; python_version < "3.11" \
--hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \
--hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16
fastapi==0.111.0 \
--hash=sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0 \
--hash=sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7
fastapi-cli==0.0.4 \
--hash=sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809 \
--hash=sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32
frozenlist==1.4.0 \
--hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \
--hash=sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251 \
--hash=sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9 \
--hash=sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b \
--hash=sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0 \
--hash=sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b \
--hash=sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c \
--hash=sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467 \
--hash=sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1 \
--hash=sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300 \
--hash=sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea \
--hash=sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab \
--hash=sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb \
--hash=sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8 \
--hash=sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62 \
--hash=sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326 \
--hash=sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c \
--hash=sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431 \
--hash=sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3 \
--hash=sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956 \
--hash=sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472 \
--hash=sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc \
--hash=sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839 \
--hash=sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b \
--hash=sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f \
--hash=sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559 \
--hash=sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b \
--hash=sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95 \
--hash=sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb \
--hash=sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963 \
--hash=sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f
h11==0.14.0 \
--hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \
--hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761
httpcore==1.0.5 \
--hash=sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61 \
--hash=sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5
httptools==0.6.1 \
--hash=sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563 \
--hash=sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142 \
--hash=sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b \
--hash=sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658 \
--hash=sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2 \
--hash=sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837 \
--hash=sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3 \
--hash=sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58 \
--hash=sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d \
--hash=sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90 \
--hash=sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0 \
--hash=sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1 \
--hash=sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2 \
--hash=sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0 \
--hash=sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc \
--hash=sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503 \
--hash=sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949 \
--hash=sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84 \
--hash=sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb \
--hash=sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a \
--hash=sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f \
--hash=sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185
httpx==0.27.0 \
--hash=sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5 \
--hash=sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5
idna==3.4 \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
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
miservice-fork==2.7.0 \
--hash=sha256:8e2c91ebe1fc4b3c63b01cbf1818e5d5833d024fd3a8311970d649ad2d49d6e6 \
--hash=sha256:8e87ef6d89adceaf3f7a98242cdae1c9135498d77f2c743ec3871d2e42bcbab8
multidict==6.0.4 \
--hash=sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9 \
--hash=sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8 \
--hash=sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03 \
--hash=sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710 \
--hash=sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569 \
--hash=sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636 \
--hash=sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49 \
--hash=sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93 \
--hash=sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0 \
--hash=sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4 \
--hash=sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc \
--hash=sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8 \
--hash=sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed \
--hash=sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98 \
--hash=sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3 \
--hash=sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe \
--hash=sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988 \
--hash=sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c \
--hash=sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c \
--hash=sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0 \
--hash=sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5 \
--hash=sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a \
--hash=sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b \
--hash=sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982 \
--hash=sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7 \
--hash=sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461 \
--hash=sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc \
--hash=sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547 \
--hash=sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0 \
--hash=sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171 \
--hash=sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba
mutagen==1.47.0 \
--hash=sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99 \
--hash=sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719
orjson==3.10.6 \
--hash=sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0 \
--hash=sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f \
--hash=sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212 \
--hash=sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43 \
--hash=sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b \
--hash=sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a \
--hash=sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd \
--hash=sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5 \
--hash=sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b \
--hash=sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143 \
--hash=sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38 \
--hash=sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5 \
--hash=sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148 \
--hash=sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e \
--hash=sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc \
--hash=sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3 \
--hash=sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34 \
--hash=sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365 \
--hash=sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5 \
--hash=sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863 \
--hash=sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba \
--hash=sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed \
--hash=sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0 \
--hash=sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a \
--hash=sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c \
--hash=sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7 \
--hash=sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb \
--hash=sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0 \
--hash=sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7 \
--hash=sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2 \
--hash=sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7
pycparser==2.21; implementation_name != "cpython" \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
pycryptodomex==3.19.0 \
--hash=sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc \
--hash=sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975 \
--hash=sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c \
--hash=sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905 \
--hash=sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d \
--hash=sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644 \
--hash=sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188 \
--hash=sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2 \
--hash=sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002 \
--hash=sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338 \
--hash=sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec \
--hash=sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56 \
--hash=sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139 \
--hash=sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0 \
--hash=sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6 \
--hash=sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40 \
--hash=sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb \
--hash=sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53 \
--hash=sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d \
--hash=sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3 \
--hash=sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2
pydantic==2.8.2 \
--hash=sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a \
--hash=sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8
pydantic-core==2.20.1 \
--hash=sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d \
--hash=sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686 \
--hash=sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482 \
--hash=sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83 \
--hash=sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6 \
--hash=sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88 \
--hash=sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86 \
--hash=sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a \
--hash=sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6 \
--hash=sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6 \
--hash=sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43 \
--hash=sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4 \
--hash=sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e \
--hash=sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203 \
--hash=sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24 \
--hash=sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc \
--hash=sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc \
--hash=sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3 \
--hash=sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98 \
--hash=sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331 \
--hash=sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2 \
--hash=sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6 \
--hash=sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688 \
--hash=sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b \
--hash=sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0 \
--hash=sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840 \
--hash=sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c \
--hash=sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd \
--hash=sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3 \
--hash=sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231 \
--hash=sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1 \
--hash=sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953 \
--hash=sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250 \
--hash=sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a \
--hash=sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20 \
--hash=sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703 \
--hash=sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac \
--hash=sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121 \
--hash=sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e \
--hash=sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b \
--hash=sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906 \
--hash=sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9 \
--hash=sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7 \
--hash=sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b \
--hash=sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e \
--hash=sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237 \
--hash=sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1 \
--hash=sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19 \
--hash=sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad \
--hash=sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0 \
--hash=sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94 \
--hash=sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312 \
--hash=sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f \
--hash=sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1 \
--hash=sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe \
--hash=sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99 \
--hash=sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a \
--hash=sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a \
--hash=sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52 \
--hash=sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c \
--hash=sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1 \
--hash=sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a \
--hash=sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f \
--hash=sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a \
--hash=sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27
pygments==2.16.1 \
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
python-dotenv==1.0.1 \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
python-multipart==0.0.9 \
--hash=sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026 \
--hash=sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215
pyyaml==6.0.1 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
rich==13.7.1 \
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \
--hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432
shellingham==1.5.4 \
--hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \
--hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
starlette==0.37.2 \
--hash=sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee \
--hash=sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823
typer==0.12.3 \
--hash=sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914 \
--hash=sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
ujson==5.10.0 \
--hash=sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e \
--hash=sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b \
--hash=sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6 \
--hash=sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd \
--hash=sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569 \
--hash=sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f \
--hash=sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51 \
--hash=sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20 \
--hash=sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf \
--hash=sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc \
--hash=sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e \
--hash=sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539 \
--hash=sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165 \
--hash=sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126 \
--hash=sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1 \
--hash=sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816 \
--hash=sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64 \
--hash=sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8 \
--hash=sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e \
--hash=sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287 \
--hash=sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3 \
--hash=sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0 \
--hash=sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043 \
--hash=sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557 \
--hash=sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e \
--hash=sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21 \
--hash=sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337 \
--hash=sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753 \
--hash=sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804 \
--hash=sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f \
--hash=sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f \
--hash=sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5 \
--hash=sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5 \
--hash=sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1 \
--hash=sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00 \
--hash=sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2 \
--hash=sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e \
--hash=sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4 \
--hash=sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8 \
--hash=sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6 \
--hash=sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1 \
--hash=sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f \
--hash=sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4 \
--hash=sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b \
--hash=sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88 \
--hash=sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518 \
--hash=sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5 \
--hash=sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770 \
--hash=sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988 \
--hash=sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1 \
--hash=sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7 \
--hash=sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a \
--hash=sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720 \
--hash=sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3 \
--hash=sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b \
--hash=sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9 \
--hash=sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1 \
--hash=sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746
urllib3==2.0.6 \
--hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \
--hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564
uvicorn[standard]==0.30.1 \
--hash=sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81 \
--hash=sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8
uvloop==0.19.0; (sys_platform != "cygwin" and sys_platform != "win32") and platform_python_implementation != "PyPy" \
--hash=sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd \
--hash=sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec \
--hash=sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc \
--hash=sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2 \
--hash=sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12 \
--hash=sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef \
--hash=sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24 \
--hash=sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428 \
--hash=sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1 \
--hash=sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849 \
--hash=sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593 \
--hash=sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd \
--hash=sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6 \
--hash=sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3 \
--hash=sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8 \
--hash=sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533 \
--hash=sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957 \
--hash=sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650 \
--hash=sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e
watchfiles==0.22.0 \
--hash=sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b \
--hash=sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31 \
--hash=sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1 \
--hash=sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab \
--hash=sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249 \
--hash=sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd \
--hash=sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6 \
--hash=sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71 \
--hash=sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171 \
--hash=sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1 \
--hash=sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1 \
--hash=sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c \
--hash=sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb \
--hash=sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27 \
--hash=sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a \
--hash=sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed \
--hash=sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84 \
--hash=sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2 \
--hash=sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797 \
--hash=sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35 \
--hash=sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6 \
--hash=sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e \
--hash=sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2 \
--hash=sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550 \
--hash=sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e \
--hash=sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c \
--hash=sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2 \
--hash=sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc \
--hash=sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96 \
--hash=sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795 \
--hash=sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f \
--hash=sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848 \
--hash=sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb \
--hash=sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232 \
--hash=sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696 \
--hash=sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2 \
--hash=sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e \
--hash=sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67 \
--hash=sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68 \
--hash=sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb \
--hash=sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be \
--hash=sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71 \
--hash=sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c \
--hash=sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da \
--hash=sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39 \
--hash=sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea \
--hash=sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a \
--hash=sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538 \
--hash=sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1 \
--hash=sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d \
--hash=sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c
websockets==12.0 \
--hash=sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b \
--hash=sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6 \
--hash=sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df \
--hash=sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b \
--hash=sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2 \
--hash=sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed \
--hash=sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd \
--hash=sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b \
--hash=sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931 \
--hash=sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30 \
--hash=sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370 \
--hash=sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be \
--hash=sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf \
--hash=sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b \
--hash=sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402 \
--hash=sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f \
--hash=sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123 \
--hash=sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603 \
--hash=sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45 \
--hash=sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558 \
--hash=sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4 \
--hash=sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480 \
--hash=sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447 \
--hash=sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8 \
--hash=sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04 \
--hash=sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c \
--hash=sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb \
--hash=sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b \
--hash=sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c \
--hash=sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92 \
--hash=sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113 \
--hash=sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f \
--hash=sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468 \
--hash=sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611 \
--hash=sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d \
--hash=sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca \
--hash=sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f \
--hash=sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2 \
--hash=sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077 \
--hash=sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2 \
--hash=sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374 \
--hash=sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc \
--hash=sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e \
--hash=sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53 \
--hash=sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399 \
--hash=sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547 \
--hash=sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3 \
--hash=sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870 \
--hash=sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5 \
--hash=sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7
yarl==1.9.2 \
--hash=sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571 \
--hash=sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7 \
--hash=sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191 \
--hash=sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea \
--hash=sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4 \
--hash=sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095 \
--hash=sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde \
--hash=sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0 \
--hash=sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528 \
--hash=sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6 \
--hash=sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be \
--hash=sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a \
--hash=sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8 \
--hash=sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6 \
--hash=sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608 \
--hash=sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82 \
--hash=sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3 \
--hash=sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d \
--hash=sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8 \
--hash=sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac \
--hash=sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8 \
--hash=sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0 \
--hash=sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb \
--hash=sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2 \
--hash=sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7 \
--hash=sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051 \
--hash=sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9 \
--hash=sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5 \
--hash=sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9 \
--hash=sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3 \
--hash=sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560
yt-dlp==2024.7.9 \
--hash=sha256:b50a595abde523b5cc84d788f97e69c642503bd673ba740f709ebf65b5ec6592 \
--hash=sha256:e19f00f9e55e90bca1c94bcaf809aa33e51634be9f0de2df84a72d3206934f94

View File

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

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python3
import re
import sys
from pathlib import Path
@@ -22,7 +21,8 @@ def update_html_version(html_files, version):
:param html_files: 需要更新的HTML文件路径的列表。
:param version: 新的版本号字符串。
"""
pattern = re.compile(r"(/static/[a-zA-Z0-9_.-]+)(\?version=[0-9.a-zA-Z_-]*)?")
pattern = re.compile(r'(/static/.*(css|js))\?version=[^"]*"')
# pattern = re.compile(r'(/static/.*html)\?version=[^"]*"')
for html_file in html_files:
if not html_file.exists():
@@ -32,7 +32,8 @@ def update_html_version(html_files, version):
html_content = html_file.read_text()
# 更新CSS和JS版本号
html_content = pattern.sub(r"\g<1>?version=" + version, html_content)
html_content = pattern.sub(rf'\g<1>?version={version}"', html_content)
# html_content = pattern.sub(fr'\g<1>"', html_content)
# 保存更改到HTML文件
html_file.write_text(html_content)
@@ -42,12 +43,9 @@ def update_html_version(html_files, version):
# 使用案例
if __name__ == "__main__":
# 导入 xiaomusic 包以获取 __version__ 变量
try:
from xiaomusic import __version__ as new_version
except ImportError as e:
print("无法导入版本号:", e)
sys.exit(1)
import time
t = str(int(time.time()))
# 指定目录
html_directory = "xiaomusic/static" # 修改为实际的HTML文件目录路径
@@ -56,4 +54,4 @@ if __name__ == "__main__":
html_files_to_update = get_html_files(html_directory)
# 执行更新
update_html_version(html_files_to_update, new_version)
update_html_version(html_files_to_update, t)

View File

@@ -1 +1 @@
__version__ = "0.3.2"
__version__ = "0.3.20"

View File

@@ -78,7 +78,67 @@ def main():
xiaomusic = XiaoMusic(config)
HttpInit(xiaomusic)
uvicorn.run(HttpApp, host=["::", "0.0.0.0"], port=config.port)
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": f"%(asctime)s [{__version__}] [%(levelname)s] %(message)s",
"datefmt": "[%X]",
"use_colors": False,
},
"access": {
"format": f"%(asctime)s [{__version__}] [%(levelname)s] %(message)s",
"datefmt": "[%X]",
},
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"access": {
"formatter": "access",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
},
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"formatter": "access",
"filename": config.log_file,
"maxBytes": 10 * 1024 * 1024,
"backupCount": 1,
},
},
"loggers": {
"uvicorn": {
"handlers": [
"default",
"file",
],
"level": "INFO",
},
"uvicorn.error": {
"level": "INFO",
},
"uvicorn.access": {
"handlers": [
"access",
"file",
],
"level": "INFO",
"propagate": False,
},
},
}
uvicorn.run(
HttpApp,
host=["::", "0.0.0.0"],
port=config.port,
log_config=LOGGING_CONFIG,
)
if __name__ == "__main__":

View File

@@ -133,6 +133,10 @@ class Config:
group_list: str = os.getenv(
"XIAOMUSIC_GROUP_LIST", ""
) # did1:group_name,did2:group_name
remove_id3tag: bool = (
os.getenv("XIAOMUSIC_REMOVE_ID3TAG", "false").lower() == "true"
)
delay_sec: int = int(os.getenv("XIAOMUSIC_DELAY_SEC", 3)) # 下一首歌延迟播放秒数
def append_keyword(self, keys, action):
for key in keys.split(","):
@@ -143,18 +147,21 @@ class Config:
def append_user_keyword(self):
for k, v in self.user_key_word_dict.items():
self.key_word_dict[k] = v
self.key_match_order.append(k)
if k not in self.key_match_order:
self.key_match_order.append(k)
def init_keyword(self):
self.key_match_order = default_key_match_order()
self.append_keyword(self.keywords_playlocal, "playlocal")
self.append_keyword(self.keywords_play, "play")
self.append_keyword(self.keywords_stop, "stop")
self.append_user_keyword()
def __post_init__(self) -> None:
if self.proxy:
validate_proxy(self.proxy)
self.append_keyword(self.keywords_playlocal, "playlocal")
self.append_keyword(self.keywords_play, "play")
self.append_keyword(self.keywords_stop, "stop")
self.append_user_keyword()
self.init_keyword()
# 保存配置到 config-example.json 文件
if self.enable_config_example:
with open("config-example.json", "w") as f:
@@ -211,3 +218,14 @@ class Config:
converted_value = self.convert_value(k, v, type_hints)
if converted_value is not None:
setattr(self, k, converted_value)
self.init_keyword()
# 获取设置文件
def getsettingfile(self):
# 兼容旧配置空的情况
if not self.conf_path:
self.conf_path = "conf"
if not os.path.exists(self.conf_path):
os.makedirs(self.conf_path)
filename = os.path.join(self.conf_path, "setting.json")
return filename

View File

@@ -2,15 +2,17 @@ import asyncio
import json
import os
import secrets
import shutil
import tempfile
from contextlib import asynccontextmanager
from dataclasses import asdict
from pathlib import Path
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, Request, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from starlette.background import BackgroundTask
from starlette.responses import FileResponse
from xiaomusic import __version__
@@ -27,9 +29,11 @@ log = None
@asynccontextmanager
async def app_lifespan(app):
if xiaomusic is not None:
task = asyncio.create_task(xiaomusic.run_forever())
asyncio.create_task(xiaomusic.run_forever())
try:
yield
task.cancel()
except Exception as e:
log.exception(f"Execption {e}")
security = HTTPBasic()
@@ -75,6 +79,14 @@ def reset_http_server():
else:
app.dependency_overrides = {}
# 更新 music 链接
app.router.routes = [route for route in app.router.routes if route.path != "/music"]
app.mount(
"/music",
StaticFiles(directory=config.music_path, follow_symlink=True),
name="music",
)
def HttpInit(_xiaomusic):
global xiaomusic, config, log
@@ -87,26 +99,10 @@ def HttpInit(_xiaomusic):
reset_http_server()
@app.get("/music/{file_path:path}")
async def read_music_file(file_path: str):
base_dir = os.path.abspath(config.music_path)
real_path = os.path.normpath(os.path.join(base_dir, file_path))
log.info(f"read_music_file. file_path:{file_path} real_path:{real_path}")
if not real_path.startswith(base_dir):
raise HTTPException(
status_code=403, detail="Access to this file is not permitted"
)
file_location = Path(real_path).resolve()
if not file_location.exists() or not file_location.is_file():
raise HTTPException(status_code=404, detail="File not found")
return FileResponse(file_location)
@app.get("/")
async def read_index():
return FileResponse("xiaomusic/static/index.html")
folder = os.path.dirname(__file__)
return FileResponse(f"{folder}/static/index.html")
@app.get("/getversion")
@@ -174,7 +170,9 @@ async def do_cmd(data: DidCmd):
return {"ret": "Did not exist"}
if len(cmd) > 0:
asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
await xiaomusic.cancel_all_tasks()
task = asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
xiaomusic.append_running_task(task)
return {"ret": "OK"}
return {"ret": "Unknow cmd"}
@@ -252,7 +250,29 @@ async def downloadjson(data: UrlInfo):
def downloadlog(Verifcation=Depends(verification)):
file_path = xiaomusic.config.log_file
if os.path.exists(file_path):
return FileResponse(path=file_path, media_type="text/plain")
# 创建一个临时文件来保存日志的快照
temp_file = tempfile.NamedTemporaryFile(delete=False)
try:
with open(file_path, "rb") as f:
shutil.copyfileobj(f, temp_file)
temp_file.close()
# 使用BackgroundTask在响应发送完毕后删除临时文件
def cleanup_temp_file(tmp_file_path):
os.remove(tmp_file_path)
background_task = BackgroundTask(cleanup_temp_file, temp_file.name)
return FileResponse(
temp_file.name,
media_type="text/plain",
filename="xiaomusic.txt",
background=background_task,
)
except Exception as e:
os.remove(temp_file.name)
raise HTTPException(
status_code=500, detail="Error capturing log file"
) from e
else:
return {"message": "File not found."}
@@ -263,9 +283,7 @@ async def playurl(did: str, url: str):
return {"ret": "Did not exist"}
log.info(f"playurl did: {did} url: {url}")
return await xiaomusic.call_main_thread_function(
xiaomusic.play_url, did=did, arg1=url
)
return await xiaomusic.play_url(did=did, arg1=url)
@app.post("/debug_play_by_music_url")

View File

@@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width">
<title>Debug For XiaoMusic</title>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=0.3.2">
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1722319766">
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script src="/static/jquery-3.7.1.min.js?version=0.3.2"></script>
<script src="/static/jquery-3.7.1.min.js?version=1722319766"></script>
<script>
var vConsole = new window.VConsole();
@@ -30,11 +30,12 @@ function postJSON() {
function sendDebugCmd() {
var cmd = $("#cmd").val();
var did = localStorage.getItem('cur_did');
$.ajax({
type: "POST",
url: "/cmd",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({cmd: cmd}),
data: JSON.stringify({did: did, cmd: cmd}),
success: () => {
},
error: () => {

View File

@@ -3,9 +3,9 @@
<head>
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="/static/jquery-3.7.1.min.js?version=0.3.2"></script>
<script src="/static/app.js?version=0.3.2"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=0.3.2">
<script src="/static/jquery-3.7.1.min.js?version=1722319766"></script>
<script src="/static/app.js?version=1722319766"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1722319766">
<!--
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
@@ -35,7 +35,7 @@ var vConsole = new window.VConsole();
<div style="display: flex; align-items: center;">
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#8e43e7" style="height: 48px; width: 48px;"><path d="M550.826667 154.666667a47.786667 47.786667 0 0 0-19.84 4.48L298.666667 298.666667H186.453333A80 80 0 0 0 106.666667 378.453333v267.093334A80 80 0 0 0 186.453333 725.333333H298.666667l232.32 139.52a47.786667 47.786667 0 0 0 19.84 4.48A46.506667 46.506667 0 0 0 597.333333 822.826667V201.173333a46.506667 46.506667 0 0 0-46.506666-46.506666zM554.666667 822.826667c0 3.413333-3.84 3.84-3.84 3.84L320 688.853333l-9.6-6.186666H186.453333A37.12 37.12 0 0 1 149.333333 645.546667V378.453333A37.12 37.12 0 0 1 186.453333 341.333333h123.946667l10.24-6.186666 229.546667-137.6s3.84 0 3.84 3.84zM667.52 346.026667a21.333333 21.333333 0 0 0 0 30.293333 192 192 0 0 1 0 271.36 21.333333 21.333333 0 0 0 0 30.293333 21.333333 21.333333 0 0 0 30.293333 0 234.666667 234.666667 0 0 0 0-331.946666 21.333333 21.333333 0 0 0-30.293333 0z"></path><path d="M804.48 219.52a21.333333 21.333333 0 0 0-30.293333 30.293333 370.986667 370.986667 0 0 1 0 524.373334 21.333333 21.333333 0 0 0 0 30.293333 21.333333 21.333333 0 0 0 30.293333 0 414.08 414.08 0 0 0 0-584.96z"></path></svg>
<input id="volume" type="range"></input>
<a href="/static/setting.html?version=0.3.2">
<a href="/static/setting.html">
<svg fill="#8e43e7" height="48px" width="48px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-11.88 -11.88 77.76 77.76" xml:space="preserve" stroke="#8e43e7" transform="rotate(0)matrix(1, 0, 0, 1, 0, 0)" stroke-width="0.00054"><g id="SVGRepo_bgCarrier" stroke-width="0" transform="translate(0,0), scale(1)"><rect x="-11.88" y="-11.88" width="77.76" height="77.76" rx="18.6624" fill="#addcff" strokewidth="0"></rect></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="1.512"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M51.22,21h-5.052c-0.812,0-1.481-0.447-1.792-1.197s-0.153-1.54,0.42-2.114l3.572-3.571 c0.525-0.525,0.814-1.224,0.814-1.966c0-0.743-0.289-1.441-0.814-1.967l-4.553-4.553c-1.05-1.05-2.881-1.052-3.933,0l-3.571,3.571 c-0.574,0.573-1.366,0.733-2.114,0.421C33.447,9.313,33,8.644,33,7.832V2.78C33,1.247,31.753,0,30.22,0H23.78 C22.247,0,21,1.247,21,2.78v5.052c0,0.812-0.447,1.481-1.197,1.792c-0.748,0.313-1.54,0.152-2.114-0.421l-3.571-3.571 c-1.052-1.052-2.883-1.05-3.933,0l-4.553,4.553c-0.525,0.525-0.814,1.224-0.814,1.967c0,0.742,0.289,1.44,0.814,1.966l3.572,3.571 c0.573,0.574,0.73,1.364,0.42,2.114S8.644,21,7.832,21H2.78C1.247,21,0,22.247,0,23.78v6.439C0,31.753,1.247,33,2.78,33h5.052 c0.812,0,1.481,0.447,1.792,1.197s0.153,1.54-0.42,2.114l-3.572,3.571c-0.525,0.525-0.814,1.224-0.814,1.966 c0,0.743,0.289,1.441,0.814,1.967l4.553,4.553c1.051,1.051,2.881,1.053,3.933,0l3.571-3.572c0.574-0.573,1.363-0.731,2.114-0.42 c0.75,0.311,1.197,0.98,1.197,1.792v5.052c0,1.533,1.247,2.78,2.78,2.78h6.439c1.533,0,2.78-1.247,2.78-2.78v-5.052 c0-0.812,0.447-1.481,1.197-1.792c0.751-0.312,1.54-0.153,2.114,0.42l3.571,3.572c1.052,1.052,2.883,1.05,3.933,0l4.553-4.553 c0.525-0.525,0.814-1.224,0.814-1.967c0-0.742-0.289-1.44-0.814-1.966l-3.572-3.571c-0.573-0.574-0.73-1.364-0.42-2.114 S45.356,33,46.168,33h5.052c1.533,0,2.78-1.247,2.78-2.78V23.78C54,22.247,52.753,21,51.22,21z M52,30.22 C52,30.65,51.65,31,51.22,31h-5.052c-1.624,0-3.019,0.932-3.64,2.432c-0.622,1.5-0.295,3.146,0.854,4.294l3.572,3.571 c0.305,0.305,0.305,0.8,0,1.104l-4.553,4.553c-0.304,0.304-0.799,0.306-1.104,0l-3.571-3.572c-1.149-1.149-2.794-1.474-4.294-0.854 c-1.5,0.621-2.432,2.016-2.432,3.64v5.052C31,51.65,30.65,52,30.22,52H23.78C23.35,52,23,51.65,23,51.22v-5.052 c0-1.624-0.932-3.019-2.432-3.64c-0.503-0.209-1.021-0.311-1.533-0.311c-1.014,0-1.997,0.4-2.761,1.164l-3.571,3.572 c-0.306,0.306-0.801,0.304-1.104,0l-4.553-4.553c-0.305-0.305-0.305-0.8,0-1.104l3.572-3.571c1.148-1.148,1.476-2.794,0.854-4.294 C10.851,31.932,9.456,31,7.832,31H2.78C2.35,31,2,30.65,2,30.22V23.78C2,23.35,2.35,23,2.78,23h5.052 c1.624,0,3.019-0.932,3.64-2.432c0.622-1.5,0.295-3.146-0.854-4.294l-3.572-3.571c-0.305-0.305-0.305-0.8,0-1.104l4.553-4.553 c0.304-0.305,0.799-0.305,1.104,0l3.571,3.571c1.147,1.147,2.792,1.476,4.294,0.854C22.068,10.851,23,9.456,23,7.832V2.78 C23,2.35,23.35,2,23.78,2h6.439C30.65,2,31,2.35,31,2.78v5.052c0,1.624,0.932,3.019,2.432,3.64 c1.502,0.622,3.146,0.294,4.294-0.854l3.571-3.571c0.306-0.305,0.801-0.305,1.104,0l4.553,4.553c0.305,0.305,0.305,0.8,0,1.104 l-3.572,3.571c-1.148,1.148-1.476,2.794-0.854,4.294c0.621,1.5,2.016,2.432,3.64,2.432h5.052C51.65,23,52,23.35,52,23.78V30.22z"></path> <path d="M27,18c-4.963,0-9,4.037-9,9s4.037,9,9,9s9-4.037,9-9S31.963,18,27,18z M27,34c-3.859,0-7-3.141-7-7s3.141-7,7-7 s7,3.141,7,7S30.859,34,27,34z"></path> </g> </g></svg>
</a>
</div>
@@ -65,7 +65,7 @@ var vConsole = new window.VConsole();
<hr>
<div class="rows">
<input id="music-url" type="text" placeholder="链接(http://ngcdn001.cnr.cn/live/zgzs/index.m3u8)"></input>
<input id="music-url" type="text" value="https://lhttp.qtfm.cn/live/4915/64k.mp3"></input>
<button id="playurl">播放链接</button>
</div>

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>M3U to JSON Converter</title>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=0.3.2">
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1722319766">
<!--
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>

View File

@@ -3,9 +3,9 @@
<head>
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="/static/jquery-3.7.1.min.js?version=0.3.2"></script>
<script src="/static/setting.js?version=0.3.2"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=0.3.2">
<script src="/static/jquery-3.7.1.min.js?version=1722319766"></script>
<script src="/static/setting.js?version=1722319766"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1722319766">
<!--
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
@@ -95,6 +95,13 @@ var vConsole = new window.VConsole();
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="remove_id3tag">去除MP3 ID3v2和填充减少播放前延迟:</label>
<select id="remove_id3tag">
<option value="true" >true</option>
<option value="false" selected>false</option>
</select>
<label for="httpauth_username">web控制台账户:</label>
<input id="httpauth_username" type="text" value=""></input>
<label for="httpauth_password">web控制台密码:</label>
@@ -123,6 +130,9 @@ var vConsole = new window.VConsole();
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0"></input>
<label for="delay_sec">下一首歌延迟播放秒数:</label>
<input id="delay_sec" type="number" value="3"></input>
<label for="stop_tts_msg">停止提示音:</label>
<input id="stop_tts_msg" type="text" value="收到,再见"></input>
<label for="keywords_playlocal">播放本地歌曲口令:</label>
@@ -147,9 +157,9 @@ var vConsole = new window.VConsole();
<a class="button" href="/downloadlog" download="xiaomusic.txt">下载日志文件</a>
<hr>
<a href="/static/m3u.html?version=0.3.2" target="_blank">m3u文件转换工具</a>
<a href="/static/m3u.html" target="_blank">m3u文件转换工具</a>
<hr>
<a href="/static/debug.html?version=0.3.2" target="_blank">调试工具</a>
<a href="/static/debug.html" target="_blank">调试工具</a>
<footer>
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>

View File

@@ -68,9 +68,8 @@ $(function(){
// 初始化显示
for (const key in data) {
if (data.hasOwnProperty(key)) {
const $element = $("#" + key);
if ($element.length && data[key] !== '') {
if ($element.length) {
if (data[key] === true) {
$element.val('true');
} else if (data[key] === false) {
@@ -79,7 +78,6 @@ $(function(){
$element.val(data[key]);
}
}
}
}
autoSelectOne();

View File

@@ -1,12 +1,15 @@
#!/usr/bin/env python3
from __future__ import annotations
import asyncio
import copy
import difflib
import logging
import mimetypes
import os
import random
import re
import shutil
import string
import tempfile
from collections.abc import AsyncIterator
@@ -15,6 +18,8 @@ from urllib.parse import urlparse
import aiohttp
import mutagen
from mutagen.id3 import ID3
from mutagen.mp3 import MP3
from requests.utils import cookiejar_from_dict
from xiaomusic.const import SUPPORT_MUSIC_TYPE
@@ -73,12 +78,21 @@ def validate_proxy(proxy_str: str) -> bool:
# 模糊搜索
def fuzzyfinder(user_input, collection):
return difflib.get_close_matches(user_input, collection, n=10, cutoff=0.1)
lower_collection = {item.lower(): item for item in collection}
user_input = user_input.lower()
matches = difflib.get_close_matches(
user_input, lower_collection.keys(), n=10, cutoff=0.1
)
return [lower_collection[match] for match in matches]
def find_best_match(user_input, collection, cutoff=0.6):
matches = difflib.get_close_matches(user_input, collection, n=1, cutoff=cutoff)
return matches[0] if matches else None
lower_collection = {item.lower(): item for item in collection}
user_input = user_input.lower()
matches = difflib.get_close_matches(
user_input, lower_collection.keys(), n=1, cutoff=cutoff
)
return lower_collection[matches[0]] if matches else None
# 歌曲排序
@@ -131,7 +145,7 @@ def traverse_music_directory(
directory, depth=10, exclude_dirs=None, support_extension=None
):
result = {}
for root, dirs, files in os.walk(directory):
for root, dirs, files in os.walk(directory, followlinks=True):
# 忽略排除的目录
dirs[:] = [d for d in dirs if d not in exclude_dirs]
@@ -181,23 +195,20 @@ async def _get_web_music_duration(session, url, start=0, end=500):
headers = {"Range": f"bytes={start}-{end}"}
async with session.get(url, headers=headers) as response:
array_buffer = await response.read()
with tempfile.NamedTemporaryFile(delete=False) as tmp:
with tempfile.NamedTemporaryFile() as tmp:
tmp.write(array_buffer)
name = tmp.name
try:
if is_mp3(url):
m = mutagen.mp3.MP3(name)
else:
m = mutagen.File(name)
duration = m.info.length
except Exception:
pass
os.remove(name)
try:
if is_mp3(url):
m = mutagen.mp3.MP3(tmp)
else:
m = mutagen.File(tmp)
duration = m.info.length
except Exception as e:
logging.error(f"Error _get_web_music_duration: {e}")
return duration
async def get_web_music_duration(url, start=0, end=500):
async def get_web_music_duration(url):
duration = 0
try:
parsed_url = urlparse(url)
@@ -220,24 +231,26 @@ async def get_web_music_duration(url, start=0, end=500):
duration = await _get_web_music_duration(session, url, start=0, end=500)
if duration <= 0:
duration = await _get_web_music_duration(
session, url, start=0, end=1000
session, url, start=0, end=3000
)
except Exception:
pass
except Exception as e:
logging.error(f"Error get_web_music_duration: {e}")
return duration, url
# 获取文件播放时长
def get_local_music_duration(filename):
async def get_local_music_duration(filename):
loop = asyncio.get_event_loop()
duration = 0
try:
if is_mp3(filename):
m = mutagen.mp3.MP3(filename)
m = await loop.run_in_executor(None, mutagen.mp3.MP3, filename)
else:
m = mutagen.File(filename)
duration = m.info.length
except Exception:
pass
m = await loop.run_in_executor(None, mutagen.File, filename)
if m and m.info:
duration = m.info.length
except Exception as e:
logging.error(f"Error getting local music duration: {e}")
return duration
@@ -286,3 +299,37 @@ def parse_str_to_dict(s, d1=",", d2=":"):
result[k] = v
return result
# remove mp3 file id3 tag and padding to reduce delay
def no_padding(info):
# this will remove all padding
return 0
def remove_id3_tags(file_path):
audio = MP3(file_path, ID3=ID3)
change = False
# 检查是否存在ID3 v2.3或v2.4标签
if audio.tags and (
audio.tags.version == (2, 3, 0) or audio.tags.version == (2, 4, 0)
):
# 构造新文件的路径
new_file_path = file_path + ".bak"
# 备份原始文件为新文件
shutil.copy(file_path, new_file_path)
# 删除ID3标签
audio.delete()
# 删除padding
audio.save(padding=no_padding)
# 保存修改后的文件
audio.save()
change = True
return change

View File

@@ -2,6 +2,7 @@
import asyncio
import json
import logging
import math
import os
import random
import re
@@ -37,8 +38,10 @@ from xiaomusic.utils import (
fuzzyfinder,
get_local_music_duration,
get_web_music_duration,
is_mp3,
parse_cookie_string,
parse_str_to_dict,
remove_id3_tags,
traverse_music_directory,
)
@@ -59,6 +62,7 @@ class XiaoMusic:
self._all_radio = {} # 电台列表
self.music_list = {} # 播放列表 key 为目录名, value 为 play_list
self.devices = {} # key 为 did
self.running_task = []
# 初始化配置
self.init_config()
@@ -81,12 +85,11 @@ class XiaoMusic:
debug_config = deepcopy_data_no_sensitive_info(self.config)
self.log.info(f"Startup OK. {debug_config}")
if self.conf_path == self.music_path:
if self.config.conf_path == self.music_path:
self.log.warning("配置文件目录和音乐目录建议设置为不同的目录")
def init_config(self):
self.music_path = self.config.music_path
self.conf_path = self.config.conf_path
self.download_path = self.config.download_path
if not self.download_path:
self.download_path = self.music_path
@@ -103,6 +106,7 @@ class XiaoMusic:
self.active_cmd = self.config.active_cmd.split(",")
self.exclude_dirs = set(self.config.exclude_dirs.split(","))
self.music_path_depth = self.config.music_path_depth
self.remove_id3tag = self.config.remove_id3tag
def update_devices(self):
self.device_id_did = {} # key 为 device_id
@@ -306,10 +310,10 @@ class XiaoMusic:
def get_filename(self, name):
if name not in self.all_music:
self.log.debug(f"get_filename not in. name:{name}")
self.log.info(f"get_filename not in. name:{name}")
return ""
filename = self.all_music[name]
self.log.debug(f"try get_filename. filename:{filename}")
self.log.info(f"try get_filename. filename:{filename}")
if os.path.exists(filename):
return filename
return ""
@@ -340,6 +344,7 @@ class XiaoMusic:
async def get_music_sec_url(self, name):
sec = 0
url = self.get_music_url(name)
self.log.info(f"get_music_sec_url. name:{name} url:{url}")
if self.is_web_radio_music(name):
self.log.info("电台不会有播放时长")
return 0, url
@@ -347,11 +352,13 @@ class XiaoMusic:
if self.is_web_music(name):
origin_url = url
duration, url = await get_web_music_duration(url)
sec = int(duration)
sec = math.ceil(duration)
self.log.info(f"网络歌曲 {name} : {origin_url} {url} 的时长 {sec}")
else:
filename = self.get_filename(name)
sec = int(get_local_music_duration(filename))
self.log.info(f"get_music_sec_url. name:{name} filename:{filename}")
duration = await get_local_music_duration(filename)
sec = math.ceil(duration)
self.log.info(f"本地歌曲 {name} : {filename} {url} 的时长 {sec}")
if sec <= 0:
@@ -361,15 +368,25 @@ class XiaoMusic:
def get_music_url(self, name):
if self.is_web_music(name):
url = self.all_music[name]
self.log.debug(f"get_music_url web music. name:{name}, url:{url}")
self.log.info(f"get_music_url web music. name:{name}, url:{url}")
return url
filename = self.get_filename(name).replace("\\", "/")
filename = self.get_filename(name)
# 移除MP3 ID3 v2标签和填充减少播放前延迟
if self.remove_id3tag and is_mp3(filename):
self.log.info(f"remove_id3tag:{self.remove_id3tag}, is_mp3:True ")
change = remove_id3_tags(filename)
if change:
self.log.info("ID3 tag removed, orgin mp3 file saved as bak")
else:
self.log.info("No ID3 tag remove needed")
filename = filename.replace("\\", "/")
if filename.startswith(self.config.music_path):
filename = filename[len(self.config.music_path) :]
if filename.startswith("/"):
filename = filename[1:]
self.log.debug(f"get_music_url local music. name:{name}, filename:{filename}")
self.log.info(f"get_music_url local music. name:{name}, filename:{filename}")
encoded_name = urllib.parse.quote(filename)
return f"http://{self.hostname}:{self.public_port}/music/{encoded_name}"
@@ -400,13 +417,14 @@ class XiaoMusic:
(name, _) = os.path.splitext(filename)
self.all_music[name] = file
all_music_by_dir[dir_name][name] = True
self.log.debug(f"_gen_all_music_list {name}:{dir_name}:{file}")
self.log.debug(self.all_music)
# self.log.debug(self.all_music)
self.music_list = {}
for dir_name, musics in all_music_by_dir.items():
self.music_list[dir_name] = list(musics.keys())
self.log.debug("dir_name:%s, list:%s", dir_name, self.music_list[dir_name])
# self.log.debug("dir_name:%s, list:%s", dir_name, self.music_list[dir_name])
try:
self._append_music_list()
@@ -454,8 +472,8 @@ class XiaoMusic:
self.music_list[list_name] = one_music_list
if self._all_radio:
self.music_list["所有电台"] = list(self._all_radio.keys())
self.log.debug(self.all_music)
self.log.debug(self.music_list)
# self.log.debug(self.all_music)
# self.log.debug(self.music_list)
except Exception as e:
self.log.exception(f"Execption {e}")
@@ -465,13 +483,6 @@ 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
filtered_keywords = [
keyword for keyword in self.config.key_match_order if "#" not in keyword
]
joined_keywords = "/".join(filtered_keywords)
self.log.info(f"语音控制已启动, 用【{joined_keywords}】开头来控制")
self.log.debug(f"key_word_dict: {self.config.key_word_dict}")
while True:
self.polling_event.set()
await self.new_record_event.wait()
@@ -497,6 +508,19 @@ class XiaoMusic:
except Exception as e:
self.log.exception(f"Execption {e}")
def append_running_task(self, task):
self.running_task.append(task)
async def cancel_all_tasks(self):
if len(self.running_task) == 0:
self.log.info("cancel_all_tasks no task")
return
for task in self.running_task:
self.log.info(f"cancel_all_tasks {task}")
task.cancel()
await asyncio.gather(*self.running_task, return_exceptions=True)
self.running_task = []
async def check_replay(self, did):
return await self.devices[did].check_replay()
@@ -708,16 +732,9 @@ class XiaoMusic:
def getconfig(self):
return self.config
# 获取设置文件
def getsettingfile(self):
if not os.path.exists(self.conf_path):
os.makedirs(self.conf_path)
filename = os.path.join(self.conf_path, "setting.json")
return filename
def try_init_setting(self):
try:
filename = self.getsettingfile()
filename = self.config.getsettingfile()
with open(filename) as f:
data = json.loads(f.read())
self.update_config_from_setting(data)
@@ -739,8 +756,7 @@ class XiaoMusic:
# 配置文件落地
def do_saveconfig(self, data):
# 默认暂时配置保存到 music 目录下
filename = self.getsettingfile()
filename = self.config.getsettingfile()
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
@@ -758,6 +774,10 @@ class XiaoMusic:
debug_config = deepcopy_data_no_sensitive_info(self.config)
self.log.info(f"update_config_from_setting ok. data:{debug_config}")
joined_keywords = "/".join(self.config.key_match_order)
self.log.info(f"语音控制已启动, 用【{joined_keywords}】开头来控制")
self.log.debug(f"key_word_dict: {self.config.key_word_dict}")
# 重新初始化
async def reinit(self, **kwargs):
self.setup_logger()
@@ -821,6 +841,7 @@ class XiaoMusicDevice:
self._playing = False
# 关机定时器
self._stop_timer = None
self._last_cmd = None
self.update_playlist()
# 初始化播放列表
@@ -832,9 +853,13 @@ class XiaoMusicDevice:
# 播放歌曲
async def play(self, name="", search_key=""):
self._last_cmd = "play"
return await self._play(name=name, search_key=search_key)
async def _play(self, name="", search_key=""):
if search_key == "" and name == "":
if self.check_play_next():
await self.play_next()
await self._play_next()
return
else:
name = self.cur_music
@@ -855,25 +880,30 @@ class XiaoMusicDevice:
# 下一首
async def play_next(self):
return await self._play_next()
async def _play_next(self):
self.log.info("开始播放下一首")
name = self.cur_music
if (
self.device.play_type == PLAY_TYPE_ALL
or self.device.play_type == PLAY_TYPE_RND
or name == ""
or (name not in self._play_list)
):
name = self.get_next_music()
self.log.info(f"play_next. name:{name}, cur_music:{self.cur_music}")
self.log.info(f"_play_next. name:{name}, cur_music:{self.cur_music}")
if name == "":
await self.do_tts("本地没有歌曲")
return
await self.play(name)
await self._play(name)
# 播放本地歌曲
async def playlocal(self, name):
self._last_cmd = "playlocal"
if name == "":
if self.check_play_next():
await self.play_next()
await self._play_next()
return
else:
name = self.cur_music
@@ -888,18 +918,30 @@ class XiaoMusicDevice:
await self._playmusic(name)
async def _playmusic(self, name):
# 取消组内所有的下一首歌曲的定时器
self.cancel_group_next_timer()
self._playing = True
self.cur_music = name
self.log.info(f"cur_music {self.cur_music}")
sec, url = await self.xiaomusic.get_music_sec_url(name)
await self.group_force_stop_xiaoai()
# await self.group_force_stop_xiaoai()
self.log.info(f"播放 {url}")
await self.group_player_play(url)
self.log.info("已经开始播放了")
results = await self.group_player_play(url)
if all(ele is None for ele in results):
self.log.info(f"播放 {name} 失败")
await asyncio.sleep(1)
if self.isplaying() and self._last_cmd != "stop":
await self._play_next()
return
self.log.info(f"{name}】已经开始播放了")
# 取消组内所有的下一首歌曲的定时器
self.cancel_group_next_timer()
# 设置下一首歌曲的播放定时器
if sec <= 1:
self.log.info(f"{name}】不会设置下一首歌的定时器")
return
sec = sec + self.config.delay_sec
await self.set_next_music_timeout(sec)
async def do_tts(self, value):
@@ -908,7 +950,7 @@ class XiaoMusicDevice:
self.log.info("do_tts no value")
return
await self.group_force_stop_xiaoai()
# await self.group_force_stop_xiaoai()
await self.text_to_speech(value)
# 最大等8秒
@@ -998,7 +1040,7 @@ class XiaoMusicDevice:
if self.isplaying() and not self.isdownloading():
# 继续播放歌曲
self.log.info("现在继续播放歌曲")
await self.play()
await self._play()
else:
self.log.info(
f"不会继续播放歌曲. isplaying:{self.isplaying()} isdownloading:{self.isdownloading()}"
@@ -1050,14 +1092,17 @@ class XiaoMusicDevice:
def check_play_next(self):
# 当前歌曲不在当前播放列表
if self.cur_music not in self._play_list:
self.log.info(f"当前歌曲 {self.cur_music} 不在当前播放列表")
return True
# 当前没我在播放的歌曲
if self.cur_music == "":
self.log.info("当前没我在播放的歌曲")
return True
else:
# 当前播放的歌曲不存在了
if not self.xiaomusic.is_music_exist(self.cur_music):
self.log.info(f"当前播放的歌曲 {self.cur_music} 不存在了")
return True
return False
@@ -1096,23 +1141,19 @@ class XiaoMusicDevice:
# 设置下一首歌曲的播放定时器
async def set_next_music_timeout(self, sec):
if sec <= 0:
return
if self._next_timer:
self._next_timer.cancel()
self.log.info("旧定时器已取消")
self.cancel_next_timer()
self._timeout = sec
async def _do_next():
await asyncio.sleep(self._timeout)
try:
await self.play_next()
self.log.info("定时器时间到了")
self._next_timer = None
await self._play_next()
except Exception as e:
self.log.error(f"Execption {e}")
self._next_timer = asyncio.ensure_future(_do_next())
self._next_timer = asyncio.create_task(_do_next())
self.log.info(f"{sec} 秒后将会播放下一首歌曲")
async def set_volume(self, volume: int):
@@ -1141,19 +1182,19 @@ class XiaoMusicDevice:
await self.do_tts(tts)
async def play_music_list(self, list_name, music_name):
self._last_cmd = "play_music_list"
self._cur_play_list = list_name
self._play_list = self.xiaomusic.music_list[list_name]
self.log.info(f"开始播放列表{list_name}")
await self.play(music_name)
await self._play(music_name)
async def stop(self, arg1=""):
self._last_cmd = "stop"
self._playing = False
if arg1 != "notts":
await self.do_tts(self.config.stop_tts_msg)
if self._next_timer:
self._next_timer.cancel()
self._next_timer = None
self.log.info("定时器已取消")
# 取消组内所有的下一首歌曲的定时器
self.cancel_group_next_timer()
await self.group_force_stop_xiaoai()
self.log.info("stop now")
@@ -1178,13 +1219,14 @@ class XiaoMusicDevice:
except Exception as e:
self.log.exception(f"Execption {e}")
self._stop_timer = asyncio.ensure_future(_do_stop())
self._stop_timer = asyncio.create_task(_do_stop())
await self.do_tts(f"收到,{minute}分钟后将关机")
def cancel_next_timer(self):
if self._next_timer:
self._next_timer.cancel()
self.log.info("下一曲定时器已取消")
self.log.info(f"下一曲定时器已取消 {self.device_id}")
self._next_timer = None
def cancel_group_next_timer(self):
devices = self.xiaomusic.get_group_devices(self.group_name)
@@ -1199,10 +1241,12 @@ class XiaoMusicDevice:
self.log.info("in cancel_all_timer")
if self._next_timer:
self._next_timer.cancel()
self._next_timer = None
self.log.info("cancel_all_timer _next_timer.cancel")
if self._stop_timer:
self._stop_timer.cancel()
self._stop_timer = None
self.log.info("cancel_all_timer _stop_timer.cancel")
@classmethod