Compare commits
321 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99c4296c7a | ||
|
|
0ae24f4810 | ||
|
|
77cca0d18c | ||
|
|
ddc24595df | ||
|
|
ce1bfb164c | ||
|
|
78dbba6c1f | ||
|
|
7eb0ab1e46 | ||
|
|
0a2e3cc579 | ||
|
|
75ec336285 | ||
|
|
a19d53f000 | ||
|
|
cdb6190e7a | ||
|
|
1aa9b58561 | ||
|
|
6a990f48d0 | ||
|
|
82e92a0380 | ||
|
|
ecce5c8848 | ||
|
|
87fb34e5c9 | ||
|
|
df3c4b7fa9 | ||
|
|
cb2af0ee9f | ||
|
|
8f9bba0ca3 | ||
|
|
b86e8d3196 | ||
|
|
65067346f3 | ||
|
|
865b412fb7 | ||
|
|
441fffd59e | ||
|
|
2ee7b956cf | ||
|
|
126bfa43a2 | ||
|
|
3a4d702070 | ||
|
|
2c7a09b9d4 | ||
|
|
74837baaef | ||
|
|
def63b2407 | ||
|
|
93d2047c7a | ||
|
|
9a1b9d1949 | ||
|
|
88fa4318dc | ||
|
|
0bc4bc8b13 | ||
|
|
ca2ddcd89c | ||
|
|
78acc35a62 | ||
|
|
323a832a9e | ||
|
|
0c99f4d537 | ||
|
|
b2737f745d | ||
|
|
464a1e1bd1 | ||
|
|
7bd613f74c | ||
|
|
3fb0a3bae9 | ||
|
|
b4f0e7b349 | ||
|
|
7c1ce81b5e | ||
|
|
052027fb50 | ||
|
|
b4d8434507 | ||
|
|
ec6018eabf | ||
|
|
bdeb2b5647 | ||
|
|
657e046379 | ||
|
|
bd6dd43737 | ||
|
|
5766919e69 | ||
|
|
0fe5f74d12 | ||
|
|
a2d6d1c357 | ||
|
|
5c61ef4b26 | ||
|
|
e57a02a192 | ||
|
|
5587411e37 | ||
|
|
95f756fb28 | ||
|
|
25ed90efa3 | ||
|
|
147bbcdd4c | ||
|
|
8dd9cfe0ac | ||
|
|
089bafd693 | ||
|
|
13b72bff97 | ||
|
|
3e959a08f0 | ||
|
|
3d1517854c | ||
|
|
e65c2ca3c9 | ||
|
|
6d7ff48913 | ||
|
|
7291826b56 | ||
|
|
fa06a7ad17 | ||
|
|
614cff3f05 | ||
|
|
a8d924e31f | ||
|
|
a9a6ead73f | ||
|
|
bd41735df2 | ||
|
|
655f0f9ab7 | ||
|
|
a003b6d0bf | ||
|
|
16f224b8e5 | ||
|
|
d4ca2c886f | ||
|
|
48b9e0a837 | ||
|
|
38df7aa6b9 | ||
|
|
29756c29a7 | ||
|
|
89287164ad | ||
|
|
81fb330db9 | ||
|
|
365954ecb0 | ||
|
|
d7fd7c43dc | ||
|
|
e48c6456fc | ||
|
|
193e1885e0 | ||
|
|
9acd5820ef | ||
|
|
119283693a | ||
|
|
dc9eb96a27 | ||
|
|
c022b5a0b1 | ||
|
|
bcbbfc5f52 | ||
|
|
3da07ce816 | ||
|
|
8629c16fe4 | ||
|
|
ac6fbd1b82 | ||
|
|
6995afed16 | ||
|
|
b49e250488 | ||
|
|
5032747f1e | ||
|
|
f749edcf16 | ||
|
|
14598aedee | ||
|
|
8845148cce | ||
|
|
1513a59726 | ||
|
|
497c1f34ef | ||
|
|
77920dffac | ||
|
|
c452136537 | ||
|
|
aa2992b5d7 | ||
|
|
7fcd3eeae5 | ||
|
|
30194272d9 | ||
|
|
1b71301b06 | ||
|
|
13bbff8d67 | ||
|
|
ae3507b811 | ||
|
|
bdfb0a4127 | ||
|
|
3d0a38cbb8 | ||
|
|
f469f63d97 | ||
|
|
6544bb2ff1 | ||
|
|
668237401e | ||
|
|
139ebf37c4 | ||
|
|
7146d61fcb | ||
|
|
6033c1a6fc | ||
|
|
e77a4fc10d | ||
|
|
bf2909d35a | ||
|
|
b3255a17ce | ||
|
|
a3140ff23a | ||
|
|
becfdbf338 | ||
|
|
8b74b664f0 | ||
|
|
0daba20885 | ||
|
|
8ac39af8cd | ||
|
|
d6fb62eb8e | ||
|
|
24ac876632 | ||
|
|
fcdb7bf035 | ||
|
|
4c927c56c0 | ||
|
|
3a7672982f | ||
|
|
a3ddca05a3 | ||
|
|
e9dba716b7 | ||
|
|
6ed5d0cb5f | ||
|
|
44819de3a5 | ||
|
|
043ad12dec | ||
|
|
cc5facdf4f | ||
|
|
0a603ad507 | ||
|
|
05a193fb4b | ||
|
|
5e604c5bac | ||
|
|
bfd1b313ab | ||
|
|
5c095b2395 | ||
|
|
5fc3348cfc | ||
|
|
42e44caf0d | ||
|
|
2e864eed7c | ||
|
|
f249edbd6a | ||
|
|
382bc7a620 | ||
|
|
a0eddd429e | ||
|
|
e60dc12a12 | ||
|
|
c9384aac08 | ||
|
|
9d9939be9f | ||
|
|
e25e1748c4 | ||
|
|
4d5d120e39 | ||
|
|
9c85daf712 | ||
|
|
01ed21f83d | ||
|
|
b3af44f42c | ||
|
|
9306a50123 | ||
|
|
54d2a5f0af | ||
|
|
8c8e8de142 | ||
|
|
20fe4739b5 | ||
|
|
774ac81b4b | ||
|
|
10a529220c | ||
|
|
7c9d48a9fa | ||
|
|
a76d526569 | ||
|
|
e2d71266c5 | ||
|
|
86110a2e65 | ||
|
|
4330f61888 | ||
|
|
4951cea269 | ||
|
|
ed1a4e77f6 | ||
|
|
40a3e24071 | ||
|
|
daeb0ae4b6 | ||
|
|
02d9987ad7 | ||
|
|
aa7b25cd33 | ||
|
|
e42537d591 | ||
|
|
410d4452d1 | ||
|
|
092dd8a532 | ||
|
|
2b6619b4da | ||
|
|
db8b90487f | ||
|
|
dec21aa57c | ||
|
|
baf9a83e50 | ||
|
|
d214cc8df3 | ||
|
|
55b8b4e966 | ||
|
|
6e8830c4e6 | ||
|
|
609cb4f10f | ||
|
|
917c6d21c8 | ||
|
|
2095ea0d45 | ||
|
|
ec8099b7a0 | ||
|
|
b077dbedce | ||
|
|
e6b030e7f1 | ||
|
|
5145590b1e | ||
|
|
329c6b26bd | ||
|
|
44860d495e | ||
|
|
7c9576874b | ||
|
|
3110c2221b | ||
|
|
a428f377d4 | ||
|
|
c1915fb6b1 | ||
|
|
cd54dae176 | ||
|
|
c72a619df0 | ||
|
|
425214d453 | ||
|
|
10693e103e | ||
|
|
8f045ceaf3 | ||
|
|
ff883142f7 | ||
|
|
91f1586ab0 | ||
|
|
cef5278f16 | ||
|
|
c923ad00f8 | ||
|
|
ec3dc578b8 | ||
|
|
c5e0d4f3ca | ||
|
|
53c6c20d5e | ||
|
|
1ffc8e7175 | ||
|
|
1c8d5fe423 | ||
|
|
6299ad3b55 | ||
|
|
e4efa0d879 | ||
|
|
2ebc0f11d2 | ||
|
|
180f28800e | ||
|
|
7f5692d6cd | ||
|
|
8d7b5337eb | ||
|
|
dcbf4330be | ||
|
|
2e53f20d80 | ||
|
|
2914ddcc36 | ||
|
|
442756e3cc | ||
|
|
b55a2a67c9 | ||
|
|
09545c7015 | ||
|
|
1f82efa2a1 | ||
|
|
e509052242 | ||
|
|
046cdade5a | ||
|
|
7f8e639b86 | ||
|
|
bfbd36f7f9 | ||
|
|
c5d623547c | ||
|
|
31c61675bf | ||
|
|
9900bd9ee9 | ||
|
|
8459494c61 | ||
|
|
9852feec81 | ||
|
|
ce79c0f0f7 | ||
|
|
51037fc714 | ||
|
|
f6b9178688 | ||
|
|
7ccbd6ce79 | ||
|
|
30926c6b79 | ||
|
|
270076b9a7 | ||
|
|
ba58d45d8b | ||
|
|
0543c92f37 | ||
|
|
f40a4c5c7b | ||
|
|
cc05933992 | ||
|
|
896eae92ff | ||
|
|
07676e8c5d | ||
|
|
4ec70a210b | ||
|
|
0b395f26ed | ||
|
|
965a8be5bb | ||
|
|
36ddfc8885 | ||
|
|
f82957c73f | ||
|
|
f1625e7d92 | ||
|
|
48797ddf8f | ||
|
|
6f67f515b2 | ||
|
|
9a0146b04e | ||
|
|
7fdb28c352 | ||
|
|
5619584481 | ||
|
|
3f0a1cb8f5 | ||
|
|
2f6105843b | ||
|
|
d71f99de53 | ||
|
|
d7385405d9 | ||
|
|
781e5ebb2f | ||
|
|
980772bf9c | ||
|
|
632e411c6e | ||
|
|
789d442029 | ||
|
|
0452d49930 | ||
|
|
19d781fa1f | ||
|
|
ad82d13a7e | ||
|
|
9c4d757dc0 | ||
|
|
e369d80875 | ||
|
|
9b0c8510a3 | ||
|
|
94fb158d7d | ||
|
|
11df6e9f0c | ||
|
|
4e8550a56c | ||
|
|
7f349410a0 | ||
|
|
6610c29fe4 | ||
|
|
3da1b8eac1 | ||
|
|
003068e62c | ||
|
|
1d12f0d508 | ||
|
|
1ee4667a79 | ||
|
|
521605e9c8 | ||
|
|
9add14408c | ||
|
|
e554ace7ae | ||
|
|
cdd0cdd237 | ||
|
|
c713d32230 | ||
|
|
8ee4e88f82 | ||
|
|
ca7679e9d3 | ||
|
|
b8c18ef33b | ||
|
|
1ce324151e | ||
|
|
faa452253c | ||
|
|
ae41ae57b3 | ||
|
|
6d3fe9381d | ||
|
|
f879c0aeb9 | ||
|
|
58ffb93d3f | ||
|
|
8e5df7094c | ||
|
|
64c2f54ff0 | ||
|
|
d1b869ae43 | ||
|
|
d3895f2632 | ||
|
|
5bf62c4b1a | ||
|
|
406e09922c | ||
|
|
ae34572d13 | ||
|
|
1e3c69ea90 | ||
|
|
3c232505f8 | ||
|
|
44177db9b6 | ||
|
|
e72ae973bc | ||
|
|
4ab3c5cbee | ||
|
|
4e532d298d | ||
|
|
3372440f4e | ||
|
|
1255239912 | ||
|
|
e401a73595 | ||
|
|
cca6e47da5 | ||
|
|
415e75d4b4 | ||
|
|
3c5573a2fc | ||
|
|
7275b59d40 | ||
|
|
a8d0631c33 | ||
|
|
3cfc96b779 | ||
|
|
489f3f1d6f | ||
|
|
a5f2fc195e | ||
|
|
393dbabf4b | ||
|
|
444e697f9d | ||
|
|
cf01039b53 | ||
|
|
a8fefc6f82 | ||
|
|
ae0b6066d9 | ||
|
|
53f5e7db8c | ||
|
|
2a1fa9f8cf |
45
.github/workflows/build-base-image.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Build Docker Base Image
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile.builder'
|
||||
- 'Dockerfile.runtime'
|
||||
- 'install_dependencies.sh'
|
||||
- '.github/workflows/build-base-image.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push runtime
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.runtime
|
||||
platforms: linux/amd64, linux/arm64, linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:runtime
|
||||
|
||||
- name: Build and push builder
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.builder
|
||||
platforms: linux/amd64, linux/arm64, linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:builder
|
||||
|
||||
73
.github/workflows/ci.yml
vendored
@@ -1,37 +1,88 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TEST_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}
|
||||
jobs:
|
||||
build-image:
|
||||
build-test-publish:
|
||||
runs-on: ubuntu-latest
|
||||
# run unless event type is pull_request
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
|
||||
- name: Build Docker image (linux/amd64)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ env.TEST_TAG }}-linux-amd64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Build Docker image (linux/arm64)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ env.TEST_TAG }}-linux-arm64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Build Docker image (linux/arm/v7)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/arm/v7
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ env.TEST_TAG }}-linux-arm-v7
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
# We test all the images on amd64 host here. This uses QEMU to emulate
|
||||
# the other platforms.
|
||||
- run: docker run --rm ${TEST_TAG}-linux-amd64 -h
|
||||
- run: docker run --rm ${TEST_TAG}-linux-arm64 -h
|
||||
- run: docker run --rm ${TEST_TAG}-linux-arm-v7 -h
|
||||
|
||||
# This will only push the previously built images.
|
||||
- name: Publish to Docker Hub
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}
|
||||
tags: ${{ env.TEST_TAG }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache-new
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: hanxi/xiaomusic
|
||||
|
||||
- name: Move cache to limit growth
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
|
||||
47
.github/workflows/fmt.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: fmt
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PDM
|
||||
uses: pdm-project/setup-pdm@v4
|
||||
- name: install ruff
|
||||
run: pip install ruff
|
||||
- name: Format code
|
||||
run: pdm fmt && pdm lint --fix
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
if [ -n "$(git diff)" ]; then
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
# Optionally, customize the user name and commit message, and can add an email as well such as Github Actions' email
|
||||
- name: Set up Git and Commit Changes
|
||||
run: |
|
||||
if [ "${{ steps.check_changes.outputs.changed }}" == "true" ]; then
|
||||
git config --local user.name "Formatter [BOT]"
|
||||
git add .
|
||||
git commit -m "Auto-format code 🧹🌟🤖"
|
||||
git push
|
||||
fi
|
||||
22
.github/workflows/release.yml
vendored
@@ -6,7 +6,7 @@ permissions:
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -17,27 +17,27 @@ jobs:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pdm-project/setup-pdm@v3
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
run: pdm publish
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
node-version: lts/*
|
||||
|
||||
- run: npx changelogithub
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
||||
- uses: pdm-project/setup-pdm@v3
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
run: pdm publish
|
||||
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
#needs: release-pypi
|
||||
# run unless event type is pull_request
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
@@ -55,6 +55,6 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
platforms: linux/amd64, linux/arm64, linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:latest, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:stable
|
||||
|
||||
5
.gitignore
vendored
@@ -25,7 +25,7 @@ share/python-wheels/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
*_bak/
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
@@ -167,3 +167,6 @@ test.sh
|
||||
conf
|
||||
setting.json
|
||||
.DS_Store
|
||||
cache
|
||||
tmp/
|
||||
xiaomusic.log.txt
|
||||
|
||||
395
CHANGELOG.md
@@ -1,3 +1,398 @@
|
||||
## v0.3.53 (2024-12-03)
|
||||
|
||||
### Fix
|
||||
|
||||
- 解决播放接口修改后播放失败的问题
|
||||
|
||||
## v0.3.52 (2024-12-03)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复播放接口参数错误的问题
|
||||
|
||||
## v0.3.51 (2024-12-03)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复空配置启动失败问题 close #284
|
||||
|
||||
## v0.3.50 (2024-12-03)
|
||||
|
||||
### Feat
|
||||
|
||||
- 修改日志文件的默认值
|
||||
- 新增修改tag缓存信息的接口 close #266
|
||||
- 新增专用的播放歌曲和播放歌单接口,解决默认口令提示词被修改了导致后台失效的问题
|
||||
- 统计设备型号
|
||||
- 页面与设置中的HOST不一致时弹窗提醒 (#281)
|
||||
- 未发现小爱设备时给予提示 (#278)
|
||||
- 优化设置页面提示
|
||||
|
||||
### Fix
|
||||
|
||||
- 更新 yt-dlp ,解决 B 站下载问题 close #279
|
||||
- 网页播放audio获取到错误url无法播放时提醒用户 (#280)
|
||||
- input标签自闭合
|
||||
|
||||
### Refactor
|
||||
|
||||
- 调整设置页面
|
||||
|
||||
## v0.3.49 (2024-11-28)
|
||||
|
||||
### Feat
|
||||
|
||||
- 临时文件目录支持配置 #99
|
||||
- 新增单曲播放和顺序播放功能 close #277
|
||||
- 设置播放类型支持配置语音提示词,定时任务支持设置播放类型
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复中文数字转换函数对'十、十一'等数字的处理 (#275)
|
||||
|
||||
## v0.3.48 (2024-11-20)
|
||||
|
||||
### Feat
|
||||
|
||||
- 支持替换默认口令,而不是追加 close #259
|
||||
- 新增自定义个歌单接口 #242
|
||||
|
||||
### Fix
|
||||
|
||||
- 锁定 PWA 应用旋转方向
|
||||
|
||||
## v0.3.47 (2024-11-14)
|
||||
|
||||
### Feat
|
||||
|
||||
- 支持 PWA 应用安装
|
||||
- 新增模糊匹配测试用例
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复 PWA 应用有密码时报错的问题
|
||||
- 修复播放顺序没有按数字排序的问题 close #249
|
||||
|
||||
## v0.3.46 (2024-11-08)
|
||||
|
||||
### Feat
|
||||
|
||||
- 升级依赖库
|
||||
|
||||
### Fix
|
||||
|
||||
- 添加依赖库 requests
|
||||
|
||||
### Refactor
|
||||
|
||||
- 依赖库已经支持分段获取静态文件,重构代码
|
||||
|
||||
## v0.3.45 (2024-11-08)
|
||||
|
||||
### Feat
|
||||
|
||||
- 定时任务支持设置音量
|
||||
- 播放歌单口令支持配置
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复定时任务报错问题
|
||||
|
||||
## v0.3.44 (2024-11-01)
|
||||
|
||||
### Feat
|
||||
|
||||
- 日志时间里加上日期
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复搜索失败的问题
|
||||
|
||||
## v0.3.43 (2024-10-30)
|
||||
|
||||
### Feat
|
||||
|
||||
- 播放列表可以删除当前歌曲(!危险操作,请在设置中心开启相关功能) (#250)
|
||||
- 插件自定义口令支持获取语音输入内容 #105
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复谷歌统计导致的卡顿问题
|
||||
- 解决挂载网盘卡死的问题
|
||||
|
||||
## v0.3.42 (2024-10-24)
|
||||
|
||||
### Fix
|
||||
|
||||
- 尝试修复缺少 libtiff.so.6 文件的问题 #244
|
||||
- 修复默认主题播放歌曲输入框空的情况
|
||||
- 尝试修复停止后自动播放的问题
|
||||
|
||||
## v0.3.41 (2024-10-17)
|
||||
|
||||
### Feat
|
||||
|
||||
- 设置默认时区为东八区 closed #236
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复获取标签信息报错问题
|
||||
- remove_id3_tags return None if no id3 tag (#238)
|
||||
- bug in del_music (#237)
|
||||
|
||||
## v0.3.40 (2024-10-16)
|
||||
|
||||
### Feat
|
||||
|
||||
- 默认主题的播放列表上显示歌曲数量
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复播放卡顿问题(谷歌统计地址无法访问的情况)
|
||||
|
||||
## v0.3.39 (2024-10-15)
|
||||
|
||||
### Feat
|
||||
|
||||
- 固定的播放列表全部初始化
|
||||
- 生产环境与开发环境接口分离、关于页面增加返回到主页的链接
|
||||
update: 支持https页面未及时更新的问题
|
||||
|
||||
### Fix
|
||||
|
||||
- pure主题 当前设备与远程设备未正确区分的问题 (#234)
|
||||
- static和doc添加basic auth (#231)
|
||||
|
||||
### Refactor
|
||||
|
||||
- 修改默认UI播放提示词 (#233)
|
||||
|
||||
## v0.3.38 (2024-10-14)
|
||||
|
||||
### Feat
|
||||
|
||||
- 播放状态接口返回当前播放列表 (#229)
|
||||
- 新增口令收藏歌曲用来收藏当前播放的歌曲
|
||||
- 默认UI搜索框动态显示 (#228)
|
||||
- 文件转换逻辑延迟到读取文件的时候 see #218
|
||||
- 重写播放组件,现在支持歌词显示了
|
||||
- 使用 /cmdstatus 接口来判断异步任务是否完成
|
||||
- 新增接口 /cmdstatus 用于查询异步任务是否执行完毕
|
||||
- XMusicPlayer播放器主题优化 (#216)
|
||||
- XMusicPlayer播放器主题 (#214)
|
||||
- 新增 yt-dlp cookies 文件参数支持
|
||||
- 新增批量下载歌曲工具
|
||||
- 新增后台网站图标
|
||||
- 加密音乐和图片访问链接 (#200)
|
||||
- 歌曲信息中的图片改为url #190
|
||||
- 新增更新提醒
|
||||
- 定时任务新增刷新播放列表接口
|
||||
- 后台设置名称优化
|
||||
- 新增按钮刷新 tag 信息
|
||||
- 新增 musicinfos 接口用于批量查询歌曲信息
|
||||
- 增加 tags 缓存 (#193)
|
||||
- 使用 opencc 将歌曲名转化为简体 (#192)
|
||||
- 搜索的歌曲存成列表供前端显示,实现额外索引 (#188)
|
||||
- 搜索多个结果,并更新“当前”播放列表 (#185)
|
||||
- musicinfo接口新增musictag参数,用于返回歌曲额外信息
|
||||
- 新增口令【播放列表第几个+列表名】来播放列表里的第几个 #158
|
||||
- 新增定时任务功能 #182
|
||||
- hostname can take protocol,域名支持 https 格式 (#181)
|
||||
|
||||
### Fix
|
||||
|
||||
- xplayer 收藏歌曲、取消收藏 (#230)
|
||||
- 修复型号M01获取对话记录时间戳的问题
|
||||
- 修复型号M01无法获取到对话记录的问题
|
||||
- 使用小爱设备播放时组件异常的问题 (#217)
|
||||
- 修复图片获取失败的问题
|
||||
- 修复 yt-dlp-cookies 报错
|
||||
- 修复自定义口令末尾多余逗号的情况
|
||||
- 修复windows下路径问题
|
||||
- 解决 L05C 无提示音问题 support MiIOService tts (#198)
|
||||
- 解决歌曲信息乱码问题
|
||||
- 修复搜索补全不生效的问题
|
||||
- 修复默认主题没有选中上次播放列表的问题
|
||||
- ffmpeg only output audio (#184)
|
||||
|
||||
### Refactor
|
||||
|
||||
- 新增清理缓存按钮
|
||||
- 优化默认UI的搜索框#226 (#227)
|
||||
- 修复告警
|
||||
- 体验优化,音乐列表缓存 (#222)
|
||||
- 修改为播放选中歌曲
|
||||
- 更新静态文件
|
||||
|
||||
### Perf
|
||||
|
||||
- 对歌曲信息中的图片缩小到300 #190
|
||||
|
||||
## v0.3.37 (2024-09-20)
|
||||
|
||||
### Feat
|
||||
|
||||
- Pure主题更新 设置中心新增主题音乐列表样式选择、夜间模式、其他多项优化 (#180)
|
||||
|
||||
## v0.3.36 (2024-09-19)
|
||||
|
||||
### Feat
|
||||
|
||||
- Pure 主题更新 (#178)
|
||||
- 支持配置获取对话记录间隔时间 #169
|
||||
- 允许在后台设置监听端口
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复开启继续播放时歌曲播放不完整问题 (#177)
|
||||
|
||||
## v0.3.35 (2024-09-18)
|
||||
|
||||
### Feat
|
||||
|
||||
- 允许跨域访问 #172
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复 Pure 主题白屏无法打开的问题 (#176)
|
||||
|
||||
## v0.3.34 (2024-09-18)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增 pure 主题 vue + elementUI (#172)
|
||||
|
||||
### Fix
|
||||
|
||||
- 主页适配移动端
|
||||
- 修复网页播放点击后没有关闭旧声音的问题 #166
|
||||
- 修复单曲循环的情况下歌曲不在当前播放列表时失效的情况
|
||||
|
||||
### Refactor
|
||||
|
||||
- 优化代码:输入框处理抖动问题,网页播放修改实现方式 see #166
|
||||
|
||||
## v0.3.33 (2024-09-15)
|
||||
|
||||
### Feat
|
||||
|
||||
- 调整页面布局
|
||||
- 支持继续播放 (#171)
|
||||
|
||||
### Fix
|
||||
|
||||
- #168 安全优化: 设置数据接口密码隐藏处理
|
||||
- 修复谷歌统计报错问题
|
||||
|
||||
### Refactor
|
||||
|
||||
- 优化谷歌统计
|
||||
|
||||
## v0.3.32 (2024-09-14)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增谷歌统计
|
||||
- 增加播放进度 (#160)
|
||||
|
||||
### Fix
|
||||
|
||||
- 优化audio_id查询方式 (#165)
|
||||
- 播放链接接口支持复杂的链接
|
||||
|
||||
## v0.3.31 (2024-09-10)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增播放上一首歌曲功能 #90
|
||||
- 新增所有歌曲列表
|
||||
- 触屏版显示歌曲名称 (#156)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复插件示例报错 #105
|
||||
- 修复当前播放歌曲没保存的问题 #90
|
||||
|
||||
## v0.3.30 (2024-09-07)
|
||||
|
||||
### Feat
|
||||
|
||||
- 修改设置按钮位置
|
||||
- 新增网页播放接口 #138
|
||||
|
||||
## v0.3.29 (2024-09-06)
|
||||
|
||||
### Feat
|
||||
|
||||
- 设置页面新增接口文档入口
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复网页开启秘密验证无法播歌的问题 #149
|
||||
|
||||
## v0.3.28 (2024-09-03)
|
||||
|
||||
### Feat
|
||||
|
||||
- 新增歌曲收藏功能 #87
|
||||
|
||||
### Fix
|
||||
|
||||
- docker下minetypes无法判断m4a
|
||||
|
||||
### Refactor
|
||||
|
||||
- ffmpeg_location 从配置里读取
|
||||
|
||||
## v0.3.27 (2024-09-02)
|
||||
|
||||
### Feat
|
||||
|
||||
- Add feature as requested in issue #143
|
||||
|
||||
### Fix
|
||||
|
||||
- 默认下载目录修改
|
||||
|
||||
### Refactor
|
||||
|
||||
- 处理 code review 问题'
|
||||
|
||||
## v0.3.26 (2024-08-17)
|
||||
|
||||
### Feat
|
||||
|
||||
- 删除网关模式
|
||||
|
||||
## v0.3.25 (2024-08-16)
|
||||
|
||||
### Feat
|
||||
|
||||
- 设置页面支持配置 use_music_api 选项
|
||||
|
||||
## v0.3.24 (2024-08-01)
|
||||
|
||||
### Fix
|
||||
|
||||
- #131 修复多设备切换时播放模式显示错误问题
|
||||
|
||||
## v0.3.23 (2024-08-01)
|
||||
|
||||
### Fix
|
||||
|
||||
- 修复部分文件获取不到播放时长问题
|
||||
- 处理安全问题
|
||||
|
||||
## v0.3.22 (2024-08-01)
|
||||
|
||||
### Feat
|
||||
|
||||
- 网关模式支持配置,默认关闭
|
||||
|
||||
### Fix
|
||||
|
||||
- 继续优化延迟问题
|
||||
|
||||
## v0.3.21 (2024-07-30)
|
||||
|
||||
### Feat
|
||||
|
||||
14
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM python:3.10 AS builder
|
||||
FROM hanxi/xiaomusic:builder AS builder
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN pip install -U pdm
|
||||
ENV PDM_CHECK_UPDATE=false
|
||||
@@ -8,20 +8,18 @@ COPY xiaomusic/ ./xiaomusic/
|
||||
COPY plugins/ ./plugins/
|
||||
COPY xiaomusic.py .
|
||||
RUN pdm install --prod --no-editable
|
||||
COPY install_dependencies.sh .
|
||||
RUN bash install_dependencies.sh
|
||||
|
||||
FROM python:3.10-slim
|
||||
FROM hanxi/xiaomusic:runtime
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/.venv /app/.venv
|
||||
COPY --from=builder /app/ffmpeg /app/ffmpeg
|
||||
COPY xiaomusic/ ./xiaomusic/
|
||||
COPY plugins/ ./plugins/
|
||||
COPY xiaomusic.py .
|
||||
COPY --from=builder /app/xiaomusic/ ./xiaomusic/
|
||||
COPY --from=builder /app/plugins/ ./plugins/
|
||||
COPY --from=builder /app/xiaomusic.py .
|
||||
ENV XIAOMUSIC_HOSTNAME=192.168.2.5
|
||||
ENV XIAOMUSIC_PORT=8090
|
||||
VOLUME /app/conf
|
||||
VOLUME /app/music
|
||||
EXPOSE 8090
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV PATH=/app/.venv/bin:$PATH
|
||||
ENTRYPOINT [".venv/bin/python3","xiaomusic.py"]
|
||||
|
||||
11
Dockerfile.builder
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM python:3.10
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN pip install -U pdm
|
||||
ENV PDM_CHECK_UPDATE=false
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml README.md .
|
||||
COPY xiaomusic/ ./xiaomusic/
|
||||
COPY plugins/ ./plugins/
|
||||
COPY xiaomusic.py .
|
||||
RUN pdm install --prod --no-editable
|
||||
|
||||
14
Dockerfile.runtime
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM python:3.10-slim
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
wget \
|
||||
xz-utils \
|
||||
libtiff6 \
|
||||
libopenjp2-7 \
|
||||
libxcb1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY install_dependencies.sh .
|
||||
RUN bash install_dependencies.sh
|
||||
372
README.md
@@ -1,4 +1,4 @@
|
||||
# xiaomusic
|
||||
# XiaoMusic: 无限听歌,解放小爱音箱
|
||||
[](https://github.com/hanxi/xiaomusic)
|
||||
[](https://hub.docker.com/r/hanxi/xiaomusic)
|
||||
[](https://hub.docker.com/r/hanxi/xiaomusic)
|
||||
@@ -6,18 +6,32 @@
|
||||
[](https://pypi.org/project/xiaomusic/)
|
||||
[](https://pypi.org/project/xiaomusic/)
|
||||
[](https://github.com/hanxi/xiaomusic/releases)
|
||||
|
||||
[](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
|
||||
[](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
|
||||
|
||||
|
||||
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
|
||||
|
||||
<https://github.com/hanxi/xiaomusic>
|
||||
|
||||
> 初次安装遇到问题请查阅 <https://github.com/hanxi/xiaomusic/issues/99> 上是否已经有解决办法。
|
||||
> [!TIP]
|
||||
> 初次安装遇到问题请查阅 [💬 FAQ问题集合](https://github.com/hanxi/xiaomusic/issues/99) ,一般遇到的问题都已经有解决办法。
|
||||
|
||||
## 最简配置运行
|
||||
## 👋 最简配置运行
|
||||
|
||||
已经支持在 web 页面配置其他参数,docker compose 配置如下:
|
||||
已经支持在 web 页面配置其他参数,docker 启动命令如下:
|
||||
|
||||
```bash
|
||||
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf hanxi/xiaomusic
|
||||
```
|
||||
|
||||
🔥 国内:
|
||||
|
||||
```bash
|
||||
docker run -p 8090:8090 -v /xiaomusic/music:/app/music -v /xiaomusic/conf:/app/conf m.daocloud.io/docker.io/hanxi/xiaomusic
|
||||
```
|
||||
|
||||
对应的 docker compose 配置如下:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@@ -28,24 +42,59 @@ services:
|
||||
ports:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
- ./conf:/app/conf
|
||||
- /xiaomusic/music:/app/music
|
||||
- /xiaomusic/conf:/app/conf
|
||||
```
|
||||
|
||||
对应的 docker 启动命令如下:
|
||||
🔥 国内:
|
||||
|
||||
```yaml
|
||||
docker run -p 8090:8090 \
|
||||
-v ./music:/app/music \
|
||||
-v ./conf:/app/conf \
|
||||
hanxi/xiaomusic
|
||||
services:
|
||||
xiaomusic:
|
||||
image: m.daocloud.io/docker.io/hanxi/xiaomusic
|
||||
container_name: xiaomusic
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- /xiaomusic/music:/app/music
|
||||
- /xiaomusic/conf:/app/conf
|
||||
```
|
||||
|
||||
其中 conf 目录为配置文件存放目录,music 目录为音乐存放目录,建议分开配置为不同的目录。
|
||||
|
||||
启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
|
||||
> [!NOTE]
|
||||
> 上面配置的 /xiaomusic/music 和 /xiaomusic/conf 是 docker 主机里的 /xiaomusic 目录下的,可以修改为其他目录。如果报错找不到 /xiaomusic/music 目录,可以先执行 `mkdir -p /xiaomusic/{music,conf}` 命令新建目录。
|
||||
|
||||
### ✨✨✨ 修改默认8090端口映射 ✨✨✨
|
||||
docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
|
||||
|
||||
> [!TIP]
|
||||
> 目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,收个辛苦费 :moneybag: 50 元一次,安装失败不收费。
|
||||
|
||||
### 🔥 修改默认8090端口映射
|
||||
|
||||
#### 方法1: 不修改监听端口 8090
|
||||
|
||||
【监听端口】保持为默认的 8090 不变,把【外网访问端口】改为 5678 。
|
||||
|
||||
```yaml
|
||||
services:
|
||||
xiaomusic:
|
||||
image: hanxi/xiaomusic
|
||||
container_name: xiaomusic
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 5678:8090
|
||||
volumes:
|
||||
- /xiaomusic/music:/app/music
|
||||
- /xiaomusic/conf:/app/conf
|
||||
environment:
|
||||
XIAOMUSIC_PUBLIC_PORT: 5678
|
||||
```
|
||||
|
||||
XIAOMUSIC_PUBLIC_PORT 对应后台设置里的【外网访问端口】,修改后可以不用重启。
|
||||
|
||||
#### 方法2: 修改监听端口 8090 为 5678
|
||||
|
||||
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
|
||||
|
||||
@@ -58,16 +107,43 @@ services:
|
||||
ports:
|
||||
- 5678:5678
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
- /xiaomusic/music:/app/music
|
||||
- /xiaomusic/conf:/app/conf
|
||||
environment:
|
||||
XIAOMUSIC_PORT: 5678
|
||||
```
|
||||
|
||||
如果不是首次修改端口,还需要修改 /xiaomusic/conf/setting.json 文件里的端口(也可以在后台修改监听端口后重启)。
|
||||
|
||||
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
|
||||
|
||||
> 目前除了 XIAOMUSIC_PORT 只能在启动前配置,其他参数都可以在 web 网页里配置。
|
||||
> [!IMPORTANT]
|
||||
> XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口,修改后记得重启。
|
||||
|
||||
## pip 方式安装运行
|
||||
|
||||
### 🤐 支持语音口令
|
||||
|
||||
- 【播放歌曲】,播放本地的歌曲
|
||||
- 【播放歌曲+歌名】,比如:播放歌曲周杰伦晴天
|
||||
- 【上一首】
|
||||
- 【下一首】
|
||||
- 【单曲循环】
|
||||
- 【全部循环】
|
||||
- 【随机播放】
|
||||
- 【关机】,【停止播放】,两个效果是一样的。
|
||||
- 【刷新列表】,当复制了歌曲进 music 目录后,可以用这个口令刷新歌单。
|
||||
- 【播放列表+列表名】,比如:播放列表其他。
|
||||
- 【加入收藏】,把当前播放的歌曲加入收藏歌单。
|
||||
- 【取消收藏】,把当前播放的歌曲从收藏歌单里移除。
|
||||
- 【播放列表收藏】,这个用于播放收藏歌单。
|
||||
- 【播放本地歌曲+歌名】,这个口令和播放歌曲的区别是本地找不到也不会去下载。
|
||||
- 【播放列表第几个+列表名】,具体见: <https://github.com/hanxi/xiaomusic/issues/158>
|
||||
- 【播放歌曲+关键词】,会搜索关键词作为临时搜索列表播放,比如说【播放歌曲林俊杰】,会播放所有林俊杰的歌。
|
||||
|
||||
> [!TIP]
|
||||
> 隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会先下载小猪佩奇的故事,然后再播放小猪佩奇的故事。
|
||||
|
||||
## 🛠️ pip 方式安装运行
|
||||
|
||||
```shell
|
||||
> pip install -U xiaomusic
|
||||
@@ -77,7 +153,7 @@ services:
|
||||
\ / | | / _` | / _ \ | |\/| | | | | | / __| | | / __|
|
||||
/ \ | | | (_| | | (_) | | | | | | |_| | \__ \ | | | (__
|
||||
/_/\_\ |_| \__,_| \___/ |_| |_| \__,_| |___/ |_| \___|
|
||||
XiaoMusic v0.1.92 by: github.com/hanxi
|
||||
XiaoMusic v0.3.37 by: github.com/hanxi
|
||||
|
||||
usage: xiaomusic [-h] [--port PORT] [--hardware HARDWARE] [--account ACCOUNT]
|
||||
[--password PASSWORD] [--cookie COOKIE] [--verbose]
|
||||
@@ -101,41 +177,35 @@ options:
|
||||
|
||||
不修改默认端口 8090 的情况下,只需要执行 `xiaomusic` 即可启动。
|
||||
|
||||
## 开发环境运行
|
||||
## 🔩 开发环境运行
|
||||
|
||||
- 使用 install_dependencies.sh 下载依赖
|
||||
- 使用 pdm 安装环境
|
||||
- 参考 [xiaogpt](https://github.com/yihong0618/xiaogpt) 设置好环境变量
|
||||
|
||||
```shell
|
||||
export MI_USER="xxxxx"
|
||||
export MI_PASS="xxxx"
|
||||
export MI_DID=00000
|
||||
export XIAOMUSIC_SEARCH='bilisearch:'
|
||||
```
|
||||
|
||||
然后启动即可。默认监听了端口 8090 , 使用其他端口自行修改。
|
||||
- 默认监听了端口 8090 , 使用其他端口自行修改。
|
||||
|
||||
```shell
|
||||
pdm run xiaomusic.py
|
||||
````
|
||||
|
||||
如果是开发前端界面,可以通过 <http://localhost:8090/docs> 查看有什么接口。
|
||||
如果是开发前端界面,可以通过 <http://localhost:8090/docs>
|
||||
查看有什么接口。目前的 web 控制台非常简陋,欢迎有兴趣的朋友帮忙实现一个漂亮的前端,需要什么接口可以随时提需求。
|
||||
|
||||
### 支持口令
|
||||
### 🚦 代码提交规范
|
||||
|
||||
- **播放歌曲**
|
||||
- **播放歌曲**+歌名 比如:播放歌曲周杰伦晴天
|
||||
- 下一首
|
||||
- 单曲循环
|
||||
- 全部循环
|
||||
- 随机播放
|
||||
- 关机
|
||||
- 停止播放
|
||||
- 刷新列表
|
||||
- 播放列表+列表名 比如:播放列表其他
|
||||
提交前请执行
|
||||
|
||||
> 隐藏玩法: 对小爱同学说播放歌曲小猪佩奇的故事,会播放小猪佩奇的故事。
|
||||
```
|
||||
pdm fmt
|
||||
pdm lint --fix
|
||||
```
|
||||
|
||||
用于检查代码和格式化代码。
|
||||
|
||||
### 本地编译 Docker Image
|
||||
|
||||
```shell
|
||||
docker build -t xiaomusic .
|
||||
```
|
||||
|
||||
## 已测试支持的设备
|
||||
|
||||
@@ -146,21 +216,26 @@ pdm run xiaomusic.py
|
||||
| S12/S12A/MDZ-25-DA | [小米AI音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.s12) |
|
||||
| LX5A | [小爱音箱 万能遥控版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx5a) |
|
||||
| LX05 | [小爱音箱Play(2019款)](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx05) |
|
||||
| L15A | [小米AI音箱(第二代)](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l15a#/) |
|
||||
| L16A | [Xiaomi Sound](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l16a) |
|
||||
| L17A | [Xiaomi Sound Pro](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l17a) |
|
||||
| LX06 | [小爱音箱Pro](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx06) |
|
||||
| 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) |
|
||||
| L09A | [小米音箱Art](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l09a) |
|
||||
| LX04 X10A X08A | 已经支持的触屏版 |
|
||||
| X08C X08E X8F M01 | 需要设置【型号兼容模式】选项为 true |
|
||||
| M01/XMYX01JY | 小米小爱音箱HD (获取对话记录的接口比较特殊) |
|
||||
|
||||
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>
|
||||
|
||||
> [!NOTE]
|
||||
> 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。
|
||||
> 目前应该所有设备类型都已经支持播放,有问题随时反馈。
|
||||
> 其他触屏版不能播放可以设置 XIAOMUSIC_USE_MUSIC_API 为 true 试试。
|
||||
> 其他触屏版不能播放可以设置【型号兼容模式】选项为 true 试试。见 <https://github.com/hanxi/xiaomusic/issues/30>
|
||||
|
||||
## 支持音乐格式
|
||||
## 🎵 支持音乐格式
|
||||
|
||||
- mp3
|
||||
- flac
|
||||
@@ -169,120 +244,13 @@ pdm run xiaomusic.py
|
||||
- ogg
|
||||
- m4a
|
||||
|
||||
> [!NOTE]
|
||||
> 本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式的。
|
||||
> 已知 L05B L05C 不支持 flac 格式。
|
||||
|
||||
## 在 Docker 里使用
|
||||
|
||||
```shell
|
||||
docker run -e MI_USER='your-xiaomi-account' \
|
||||
-e MI_PASS='your-xiaomi-password' \
|
||||
-e MI_DID='your-xiaomi-speaker-mid' \
|
||||
-e MI_HARDWARE='L07A' \
|
||||
-e XIAOMUSIC_PROXY='proxy-for-yt-dlp' \
|
||||
-e XIAOMUSIC_HOSTNAME=192.168.2.5 \
|
||||
-e XIAOMUSIC_SEARCH='bilisearch:' \
|
||||
-p 8090:8090 \
|
||||
-v ./music:/app/music hanxi/xiaomusic
|
||||
```
|
||||
|
||||
- XIAOMUSIC_SEARCH 可以配置为 'bilisearch:' 表示歌曲从哔哩哔哩下载;
|
||||
- 配置为 'ytsearch:' 表示歌曲从 youtube 下载。
|
||||
- XIAOMUSIC_PROXY 用于配置代理,默认为空;
|
||||
- 当 XIAOMUSIC_SEARCH 配置为 'ytsearch:' 时在国内需要用到。
|
||||
- MI_HARDWARE 是小米音箱的型号,默认为'L07A'
|
||||
- 注意端口必须映射为与容器内一致, XIAOMUSIC_HOSTNAME 需要设置为宿主机的 IP 地址,否则小爱无法正常播放。
|
||||
- 可以把 /app/music 目录映射到本地,用于保存下载的歌曲。
|
||||
|
||||
XIAOMUSIC_PROXY 参数格式参考 yt-dlp 文档说明:
|
||||
```
|
||||
Use the specified HTTP/HTTPS/SOCKS proxy. To
|
||||
enable SOCKS proxy, specify a proper scheme,
|
||||
e.g. socks5://user:pass@127.0.0.1:1080/.
|
||||
Pass in an empty string (--proxy "") for
|
||||
direct connection
|
||||
```
|
||||
|
||||
见 <https://github.com/hanxi/xiaomusic/issues/2> 和 <https://github.com/hanxi/xiaomusic/issues/11>
|
||||
|
||||
### 本地编译Docker Image
|
||||
|
||||
```shell
|
||||
docker build -t xiaomusic .
|
||||
```
|
||||
|
||||
### docker compose 示例
|
||||
|
||||
使用哔哩哔哩下载歌曲:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
xiaomusic:
|
||||
image: hanxi/xiaomusic
|
||||
container_name: xiaomusic
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
environment:
|
||||
MI_USER: '小米账号'
|
||||
MI_PASS: '小米密码'
|
||||
MI_DID: 00000
|
||||
MI_HARDWARE: 'L07A'
|
||||
XIAOMUSIC_SEARCH: 'bilisearch:'
|
||||
XIAOMUSIC_HOSTNAME: '192.168.2.5'
|
||||
```
|
||||
> 已知 L05B L05C LX06 L16A 不支持 flac 格式。
|
||||
> 如果格式不能播放可以打开【转换为MP3】和【型号兼容模式】选项。具体见 <https://github.com/hanxi/xiaomusic/issues/153#issuecomment-2328168689>
|
||||
|
||||
|
||||
使用 youtobe 下载歌曲:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
xiaomusic:
|
||||
image: hanxi/xiaomusic
|
||||
container_name: xiaomusic
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
environment:
|
||||
MI_USER: '小米账号'
|
||||
MI_PASS: '小米密码'
|
||||
MI_DID: 00000
|
||||
MI_HARDWARE: 'L07A'
|
||||
XIAOMUSIC_SEARCH: 'ytsearch:'
|
||||
XIAOMUSIC_PROXY: 'http://192.168.2.5:8080'
|
||||
XIAOMUSIC_HOSTNAME: '192.168.2.5'
|
||||
```
|
||||
|
||||
如果想让 setting.json 文件不存储到 music 目录,可以这样配,下面的示例会把 setting.json 文件放到容器的 /app/conf 目录且映射到本地的 ./conf 目录:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
xiaomusic:
|
||||
image: hanxi/xiaomusic
|
||||
container_name: xiaomusic
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8090:8090
|
||||
volumes:
|
||||
- ./music:/app/music
|
||||
- ./conf:/app/conf
|
||||
environment:
|
||||
MI_USER: '小米账号'
|
||||
MI_PASS: '小米密码'
|
||||
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
|
||||
XIAOMUSIC_CONF_PATH: '/app/conf'
|
||||
```
|
||||
|
||||
|
||||
## 简易的控制面板
|
||||
## 💡 简易的控制面板
|
||||
|
||||
浏览器进入 <http://192.168.2.5:8090>
|
||||
|
||||
@@ -297,73 +265,93 @@ services:
|
||||
- 配置网络歌单
|
||||
- 日志文件下载
|
||||
|
||||
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
|
||||
- MI_USER
|
||||
- MI_PASS
|
||||
- XIAOMUSIC_HOSTNAME
|
||||
采用新的设置页面之后,没有必须在启动前配置的环境变量了,除非是改默认的 8090 端口才需要配置环境变量。
|
||||
|
||||
其他的这些可以在网页里配置:
|
||||
- MI_DID
|
||||
- MI_HARDWARE
|
||||
- XIAOMUSIC_SEARCH
|
||||
- XIAOMUSIC_PROXY
|
||||
|
||||
## 网络歌单功能
|
||||
## 🌏 网络歌单功能
|
||||
|
||||
可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接,同时配备了 m3u 文件格式转换工具,可以很方便的把 m3u 电台文件转换成网络歌单格式的 json 文件,具体用法见 <https://github.com/hanxi/xiaomusic/issues/78>
|
||||
|
||||
> [!NOTE]
|
||||
> 欢迎有想法的朋友们制作更多的歌单转换工具。
|
||||
|
||||
## 更多其他可选配置
|
||||
## 🍺 更多其他可选配置
|
||||
|
||||
- XIAOMUSIC_ACTIVE_CMD 环境变量,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 <https://github.com/hanxi/xiaomusic/pull/43>
|
||||
- XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录
|
||||
- XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,具体见 <https://github.com/hanxi/xiaomusic/issues/76>
|
||||
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,具体见 <https://github.com/hanxi/xiaomusic/issues/47>
|
||||
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户
|
||||
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
|
||||
- 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 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,支持模糊匹配歌名和歌单名。 具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||
- XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准。具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||
- XIAOMUSIC_PUBLIC_PORT 用于设置外网端口,当使用反向代理时可以设置为外网端口,XIAOMUSIC_HOSTNAME 设为外网IP或者域名即可。
|
||||
- XIAOMUSIC_DOWNLOAD_PATH 变量可以配置下载目录,默认为空,表示使用 music 目录为下载目录。设置这个目录必须是 music 的子目录,否则刷新列表后会找不到歌曲。具体见 <https://github.com/hanxi/xiaomusic/issues/98>
|
||||
- XIAOMUSIC_ACTIVE_CMD 环境变量,对应后台的 【允许唤醒的命令】,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 <https://github.com/hanxi/xiaomusic/pull/43>
|
||||
- XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录,对应后台的 【忽略目录】
|
||||
- XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,对应后台的 【目录深度】,具体见 <https://github.com/hanxi/xiaomusic/issues/76>
|
||||
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,对应后台的 【关闭密码验证】,具体见 <https://github.com/hanxi/xiaomusic/issues/47>
|
||||
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户,对应后台的 【控制台账户】
|
||||
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码,对应后台的 【控制台密码】
|
||||
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,对应后台的 【配置文件目录】,记得把目录映射到主机,默认为 `/app/config` ,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
|
||||
- XIAOMUSIC_CACHE_DIR 用来音乐 tag 缓存,默认为 `/app/cache`,对应后台的 【缓存文件目录】。
|
||||
- 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 用来播放歌曲的口令前缀,对应后台的 【播放歌曲口令】,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,对应后台的 【停止口令】,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,对应后台的 【播放本地歌曲口令】,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,对应后台的 【开启模糊搜索】,支持模糊匹配歌名和歌单名。 具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||
- XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准,对应后台的 【模糊匹配阈值】。具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||
- XIAOMUSIC_PUBLIC_PORT 用于设置外网端口,对应后台的 【外网访问端口】,当使用反向代理时可以设置为外网端口,XIAOMUSIC_HOSTNAME 设为外网IP或者域名即可。
|
||||
- XIAOMUSIC_DOWNLOAD_PATH 变量可以配置下载目录,默认为空,表示使用 music 目录为下载目录,对应后台的 【音乐下载目录】。设置这个目录必须是 music 的子目录,否则刷新列表后会找不到歌曲。具体见 <https://github.com/hanxi/xiaomusic/issues/98>
|
||||
- XIAOMUSIC_PROXY 用于配置国内使用 youtube 源下载歌曲时使用的代理,参数格式参考 yt-dlp 文档说明。 见 <https://github.com/hanxi/xiaomusic/issues/2> 和 <https://github.com/hanxi/xiaomusic/issues/11>
|
||||
- MIIO_TTS_CMD 用于部分机型(如:`L05C`)使用 MiIO 支持 tts 能力,默认为空,命令选择见 [MiService-fork 文档](https://github.com/yihong0618/MiService)
|
||||
|
||||
## 高级篇
|
||||
### ⚠️ 安全提醒
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 如果配置了公网访问 xiaomusic ,请一定要开启密码登陆,并设置复杂的密码。且不要在公共场所的 WiFi 环境下使用,否则可能造成小米账号密码泄露。
|
||||
|
||||
## 🤔 高级篇
|
||||
|
||||
- 自定义口令功能 <https://github.com/hanxi/xiaomusic/issues/105>
|
||||
- [ ] 缺少一篇教程 [如何写自定义插件](https://github.com/hanxi/xiaomusic/issues/105)
|
||||
|
||||
## 讨论区
|
||||
## 📢 讨论区
|
||||
|
||||
- [点击链接加入QQ频道【xiaomusic】](https://pd.qq.com/s/e2jybz0ss)
|
||||
- [点击链接加入群聊【xiaomusic】 604526973](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=13St5PLVcTxYlWTAs_iAawazjtdD1l-a&authKey=dJWEpaT2fDBDpdUUOWj%2FLt6NS1ePBfShDfz7a6seNURi05VvVnAGQzXF%2FM%2F5HgIm&noverify=0&group_code=604526973)
|
||||
- <https://github.com/hanxi/xiaomusic/issues>
|
||||
- [微信群二维码](https://github.com/hanxi/xiaomusic/issues/86)
|
||||
|
||||
## 感谢
|
||||
## ❤️ 感谢
|
||||
|
||||
- [xiaomi](https://www.mi.com/)
|
||||
- [PDM](https://pdm.fming.dev/latest/)
|
||||
- [xiaogpt](https://github.com/yihong0618/xiaogpt)
|
||||
- [MiService](https://github.com/yihong0618/MiService)
|
||||
- [实现原理](https://github.com/yihong0618/gitblog/issues/258)
|
||||
- [yt-dlp](https://github.com/yt-dlp/yt-dlp)
|
||||
- [NAS部署教程](https://post.m.smzdm.com/p/avpe7n99/)
|
||||
- [群晖部署教程](https://post.m.smzdm.com/p/a7px7dol/)
|
||||
- [QNAS部署教程](https://post.smzdm.com/p/a5xz5x63/)
|
||||
- [awesome-xiaoai](https://github.com/zzz6519003/awesome-xiaoai)
|
||||
- [微信小程序: XIAO晓音](https://github.com/F-loat/xiaoplayer)
|
||||
- [pure 主题 xiaomusicUI](https://github.com/52fisher/xiaomusicUI)
|
||||
- [移动端的播放器主题](https://github.com/52fisher/XMusicPlayer)
|
||||
- [一个第三方的主题](https://github.com/DarrenWen/xiaomusicui)
|
||||
- 所有帮忙调试和测试的朋友
|
||||
- 所有反馈问题和建议的朋友
|
||||
|
||||
### 👉 其他教程
|
||||
|
||||
更多功能见 [📝 文档汇总](https://github.com/hanxi/xiaomusic/issues/211)
|
||||
|
||||
## 🚨 免责声明
|
||||
|
||||
本项目仅供学习和研究目的,不得用于任何商业活动。用户在使用本项目时应遵守所在地区的法律法规,对于违法使用所导致的后果,本项目及作者不承担任何责任。
|
||||
本项目可能存在未知的缺陷和风险(包括但不限于设备损坏和账号封禁等),使用者应自行承担使用本项目所产生的所有风险及责任。
|
||||
作者不保证本项目的准确性、完整性、及时性、可靠性,也不承担任何因使用本项目而产生的任何损失或损害责任。
|
||||
使用本项目即表示您已阅读并同意本免责声明的全部内容。
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#hanxi/xiaomusic&Date)
|
||||
|
||||
## 赞赏
|
||||
|
||||
- 爱发电 <https://afdian.net/a/imhanxi>
|
||||
- 点个 Star ⭐
|
||||
- 谢谢 ❤️
|
||||
- :moneybag: 爱发电 <https://afdian.com/a/imhanxi>
|
||||
- 点个 Star :star:
|
||||
- 谢谢 :heart:
|
||||
- 
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/hanxi/xiaomusic/blob/main/LICENSE) License © 2024 涵曦
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
"account": "",
|
||||
"password": "",
|
||||
"mi_did": "",
|
||||
"miio_tts_command": null,
|
||||
"cookie": "",
|
||||
"verbose": false,
|
||||
"music_path": "music",
|
||||
"download_path": "",
|
||||
"conf_path": null,
|
||||
"tag_cache_dir": null,
|
||||
"hostname": "192.168.2.5",
|
||||
"port": 8090,
|
||||
"public_port": 0,
|
||||
@@ -77,5 +79,6 @@
|
||||
},
|
||||
"enable_force_stop": false,
|
||||
"devices": {},
|
||||
"group_list": ""
|
||||
}
|
||||
"group_list": "",
|
||||
"convert_to_mp3": false
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ install_from_github() {
|
||||
mkdir -p ffmpeg/bin
|
||||
mv $pkg/bin/ffmpeg ffmpeg/bin/
|
||||
mv $pkg/bin/ffprobe ffmpeg/bin/
|
||||
rm -rf $pkg $pkg.tar.xz
|
||||
}
|
||||
|
||||
install_from_ffmpeg() {
|
||||
@@ -26,6 +27,7 @@ install_from_ffmpeg() {
|
||||
mkdir -p ffmpeg/bin
|
||||
mv $pkg/*/ffmpeg ffmpeg/bin/
|
||||
mv $pkg/*/ffprobe ffmpeg/bin/
|
||||
rm -rf $pkg $pkg.tar.xz
|
||||
}
|
||||
|
||||
# 基于架构执行不同的操作
|
||||
|
||||
628
pdm.lock
generated
@@ -5,7 +5,7 @@
|
||||
groups = ["default", "dev", "lint"]
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:0a0b1f63fdd9dd2c4ca2a777f12d294126a880631c1b3d48108d1df283ba14a8"
|
||||
content_hash = "sha256:d22d4b03450cd369683d3ddf827359cd96541ee09e29bfa1fa666d72aa7d3ab0"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = "==3.10.12"
|
||||
@@ -25,24 +25,38 @@ files = [
|
||||
{file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
version = "2.3.4"
|
||||
requires_python = "<4.0,>=3.8"
|
||||
summary = "Happy Eyeballs for asyncio"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "aiohappyeyeballs-2.3.4-py3-none-any.whl", hash = "sha256:40a16ceffcf1fc9e142fd488123b2e218abc4188cf12ac20c67200e1579baa42"},
|
||||
{file = "aiohappyeyeballs-2.3.4.tar.gz", hash = "sha256:7e1ae8399c320a8adec76f6c919ed5ceae6edd4c3672f4d9eae2b27e37c80ff6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.9.5"
|
||||
requires_python = ">=3.8"
|
||||
version = "3.11.9"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Async http client/server framework (asyncio)"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"aiohappyeyeballs>=2.3.0",
|
||||
"aiosignal>=1.1.2",
|
||||
"async-timeout<5.0,>=4.0; python_version < \"3.11\"",
|
||||
"async-timeout<6.0,>=4.0; python_version < \"3.11\"",
|
||||
"attrs>=17.3.0",
|
||||
"frozenlist>=1.1.1",
|
||||
"multidict<7.0,>=4.5",
|
||||
"yarl<2.0,>=1.0",
|
||||
"propcache>=0.2.0",
|
||||
"yarl<2.0,>=1.17.0",
|
||||
]
|
||||
files = [
|
||||
{file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"},
|
||||
{file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"},
|
||||
{file = "aiohttp-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39625703540feb50b6b7f938b3856d1f4886d2e585d88274e62b1bd273fae09b"},
|
||||
{file = "aiohttp-3.11.9.tar.gz", hash = "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -93,6 +107,22 @@ files = [
|
||||
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "apscheduler"
|
||||
version = "3.11.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "In-process task scheduler with Cron-like capabilities"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"backports-zoneinfo; python_version < \"3.9\"",
|
||||
"tzlocal>=3.0",
|
||||
]
|
||||
files = [
|
||||
{file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"},
|
||||
{file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argcomplete"
|
||||
version = "3.4.0"
|
||||
@@ -142,55 +172,20 @@ summary = "Python bindings for the Brotli compression library"
|
||||
groups = ["default"]
|
||||
marker = "implementation_name == \"cpython\" and python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"},
|
||||
{file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"},
|
||||
{file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
|
||||
{file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
|
||||
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.7.4"
|
||||
version = "2024.8.30"
|
||||
requires_python = ">=3.6"
|
||||
summary = "Python package for providing Mozilla's CA Bundle."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -280,13 +275,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "commitizen"
|
||||
version = "3.28.0"
|
||||
requires_python = ">=3.8"
|
||||
version = "4.0.0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Python commitizen client tool"
|
||||
groups = ["dev"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"argcomplete<3.5,>=1.12.1",
|
||||
"argcomplete<3.6,>=1.12.1",
|
||||
"charset-normalizer<4,>=2.1.0",
|
||||
"colorama<0.5.0,>=0.4.1",
|
||||
"decli<0.7.0,>=0.6.0",
|
||||
@@ -297,11 +292,11 @@ dependencies = [
|
||||
"questionary<3.0,>=2.0",
|
||||
"termcolor<3,>=1.1",
|
||||
"tomlkit<1.0.0,>=0.5.3",
|
||||
"typing-extensions<5.0.0,>=4.0.1; python_version < \"3.8\"",
|
||||
"typing-extensions<5.0.0,>=4.0.1; python_version < \"3.11\"",
|
||||
]
|
||||
files = [
|
||||
{file = "commitizen-3.28.0-py3-none-any.whl", hash = "sha256:372dba10bb082be0e571cfa9ac0946f4333c3f4b2e242fbe32ae2f2afea143e4"},
|
||||
{file = "commitizen-3.28.0.tar.gz", hash = "sha256:de3a90b3246233260649e423963cd702d56a3b499ea02886a6412ebfb76f9462"},
|
||||
{file = "commitizen-4.0.0-py3-none-any.whl", hash = "sha256:52873ee589a64cf77fc55570dbd3f987c6ffcd33132d179eb625c4d06ae935f7"},
|
||||
{file = "commitizen-4.0.0.tar.gz", hash = "sha256:16aff27e01b43015eab1c74eabbca3e284b4988dd1b146a0963282db241dc2c0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -316,34 +311,6 @@ files = [
|
||||
{file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.6.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "DNS toolkit"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
|
||||
{file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.2.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A robust email address syntax and deliverability validation library."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"dnspython>=2.0.0",
|
||||
"idna>=2.0.0",
|
||||
]
|
||||
files = [
|
||||
{file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"},
|
||||
{file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
@@ -358,40 +325,19 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.111.1"
|
||||
version = "0.115.5"
|
||||
requires_python = ">=3.8"
|
||||
summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"email-validator>=2.0.0",
|
||||
"fastapi-cli>=0.0.2",
|
||||
"httpx>=0.23.0",
|
||||
"jinja2>=2.11.2",
|
||||
"pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4",
|
||||
"python-multipart>=0.0.7",
|
||||
"starlette<0.38.0,>=0.37.2",
|
||||
"starlette<0.42.0,>=0.40.0",
|
||||
"typing-extensions>=4.8.0",
|
||||
"uvicorn[standard]>=0.12.0",
|
||||
]
|
||||
files = [
|
||||
{file = "fastapi-0.111.1-py3-none-any.whl", hash = "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf"},
|
||||
{file = "fastapi-0.111.1.tar.gz", hash = "sha256:ddd1ac34cb1f76c2e2d7f8545a4bcb5463bce4834e81abf0b189e0c359ab2413"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi-cli"
|
||||
version = "0.0.4"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"typer>=0.12.3",
|
||||
]
|
||||
files = [
|
||||
{file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"},
|
||||
{file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"},
|
||||
{file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"},
|
||||
{file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -451,6 +397,18 @@ files = [
|
||||
{file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ga4mp"
|
||||
version = "2.0.4"
|
||||
requires_python = ">=3.6,<4.0"
|
||||
summary = "Google Analytics 4 Measurement Protocol Python Module"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "ga4mp-2.0.4-py3-none-any.whl", hash = "sha256:11e5072b33a93917bbfcf5b44ee48d21e45124ef18e2a0f1275e1529df340de8"},
|
||||
{file = "ga4mp-2.0.4.tar.gz", hash = "sha256:2fdcf275e643c5c3ab2c3a03e82edc3551109f7e9175b3604aea8c8e015c15ad"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
@@ -466,73 +424,6 @@ files = [
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.5"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A minimal low-level HTTP client."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"certifi",
|
||||
"h11<0.15,>=0.13",
|
||||
]
|
||||
files = [
|
||||
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
|
||||
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.6.1"
|
||||
requires_python = ">=3.8.0"
|
||||
summary = "A collection of framework independent HTTP protocol utils."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"},
|
||||
{file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"},
|
||||
{file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"},
|
||||
{file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"},
|
||||
{file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.27.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "The next generation HTTP client."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"anyio",
|
||||
"certifi",
|
||||
"httpcore==1.*",
|
||||
"idna",
|
||||
"sniffio",
|
||||
]
|
||||
files = [
|
||||
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
|
||||
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.7"
|
||||
@@ -550,7 +441,7 @@ name = "jinja2"
|
||||
version = "3.1.4"
|
||||
requires_python = ">=3.7"
|
||||
summary = "A very fast and expressive template engine."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["dev"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"MarkupSafe>=2.0",
|
||||
@@ -580,7 +471,7 @@ name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Safely add untrusted strings to HTML/XML markup."
|
||||
groups = ["default", "dev"]
|
||||
groups = ["dev"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
|
||||
@@ -630,7 +521,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "miservice-fork"
|
||||
version = "2.7.0"
|
||||
version = "2.7.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "XiaoMi Cloud Service fork from https://github.com/Yonsm/MiService"
|
||||
groups = ["default"]
|
||||
@@ -641,8 +532,8 @@ dependencies = [
|
||||
"rich",
|
||||
]
|
||||
files = [
|
||||
{file = "miservice_fork-2.7.0-py3-none-any.whl", hash = "sha256:8e2c91ebe1fc4b3c63b01cbf1818e5d5833d024fd3a8311970d649ad2d49d6e6"},
|
||||
{file = "miservice_fork-2.7.0.tar.gz", hash = "sha256:8e87ef6d89adceaf3f7a98242cdae1c9135498d77f2c743ec3871d2e42bcbab8"},
|
||||
{file = "miservice_fork-2.7.1-py3-none-any.whl", hash = "sha256:0f908de6db2c77bb1a27b217cf3ab99835b408804cda5f7d16329e8e60948d13"},
|
||||
{file = "miservice_fork-2.7.1.tar.gz", hash = "sha256:a10c82e353dcc69183e50ce00a50cb602d9dc5992d83989b4a259d4830b48ea1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -714,6 +605,17 @@ files = [
|
||||
{file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opencc-python-reimplemented"
|
||||
version = "0.1.7"
|
||||
summary = "OpenCC made with Python"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "opencc-python-reimplemented-0.1.7.tar.gz", hash = "sha256:4f777ea3461a25257a7b876112cfa90bb6acabc6dfb843bf4d11266e43579dee"},
|
||||
{file = "opencc_python_reimplemented-0.1.7-py2.py3-none-any.whl", hash = "sha256:41b3b92943c7bed291f448e9c7fad4b577c8c2eae30fcfe5a74edf8818493aa6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
@@ -726,6 +628,19 @@ files = [
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "11.0.0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Python Imaging Library (Fork)"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"},
|
||||
{file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.36"
|
||||
@@ -741,29 +656,29 @@ files = [
|
||||
{file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "propcache"
|
||||
version = "0.2.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Accelerated property cache"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"},
|
||||
{file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"},
|
||||
{file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodomex"
|
||||
version = "3.20.0"
|
||||
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "3.21.0"
|
||||
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
summary = "Cryptographic library for Python"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"},
|
||||
{file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"},
|
||||
{file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"},
|
||||
{file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"},
|
||||
{file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"},
|
||||
{file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"},
|
||||
{file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"},
|
||||
{file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"},
|
||||
{file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -866,28 +781,16 @@ files = [
|
||||
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
|
||||
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.9"
|
||||
version = "0.0.19"
|
||||
requires_python = ">=3.8"
|
||||
summary = "A streaming multipart parser for Python"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
|
||||
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
|
||||
{file = "python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d"},
|
||||
{file = "python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -895,7 +798,7 @@ name = "pyyaml"
|
||||
version = "6.0.1"
|
||||
requires_python = ">=3.6"
|
||||
summary = "YAML parser and emitter for Python"
|
||||
groups = ["default", "dev"]
|
||||
groups = ["dev"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
|
||||
@@ -976,26 +879,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.5"
|
||||
version = "0.8.1"
|
||||
requires_python = ">=3.7"
|
||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
groups = ["lint"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"},
|
||||
{file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Tool to Detect Surrounding Shell"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
|
||||
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
|
||||
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
|
||||
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1012,7 +903,7 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.37.2"
|
||||
version = "0.41.3"
|
||||
requires_python = ">=3.8"
|
||||
summary = "The little ASGI library that shines."
|
||||
groups = ["default"]
|
||||
@@ -1022,8 +913,8 @@ dependencies = [
|
||||
"typing-extensions>=3.10.0; python_version < \"3.10\"",
|
||||
]
|
||||
files = [
|
||||
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
|
||||
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
|
||||
{file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"},
|
||||
{file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1050,51 +941,49 @@ files = [
|
||||
{file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.12.3"
|
||||
requires_python = ">=3.7"
|
||||
summary = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"click>=8.0.0",
|
||||
"rich>=10.11.0",
|
||||
"shellingham>=1.3.0",
|
||||
"typing-extensions>=3.7.4.3",
|
||||
]
|
||||
files = [
|
||||
{file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
|
||||
{file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
groups = ["default"]
|
||||
groups = ["default", "dev"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "5.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "tzinfo object for the local timezone"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"backports-zoneinfo; python_version < \"3.9\"",
|
||||
"tzdata; platform_system == \"Windows\"",
|
||||
]
|
||||
files = [
|
||||
{file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
|
||||
{file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
requires_python = ">=3.8"
|
||||
summary = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
{file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
|
||||
{file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.30.3"
|
||||
version = "0.32.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "The lightning-fast ASGI server."
|
||||
groups = ["default"]
|
||||
@@ -1105,116 +994,8 @@ dependencies = [
|
||||
"typing-extensions>=4.0; python_version < \"3.11\"",
|
||||
]
|
||||
files = [
|
||||
{file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"},
|
||||
{file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.30.3"
|
||||
extras = ["standard"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "The lightning-fast ASGI server."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"colorama>=0.4; sys_platform == \"win32\"",
|
||||
"httptools>=0.5.0",
|
||||
"python-dotenv>=0.13",
|
||||
"pyyaml>=5.1",
|
||||
"uvicorn==0.30.3",
|
||||
"uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"",
|
||||
"watchfiles>=0.13",
|
||||
"websockets>=10.4",
|
||||
]
|
||||
files = [
|
||||
{file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"},
|
||||
{file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.19.0"
|
||||
requires_python = ">=3.8.0"
|
||||
summary = "Fast implementation of asyncio event loop on top of libuv"
|
||||
groups = ["default"]
|
||||
marker = "(sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\" and python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"},
|
||||
{file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"},
|
||||
{file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"},
|
||||
{file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"},
|
||||
{file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.22.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Simple, modern and high performance file watching and code reload in python."
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"anyio>=3.0.0",
|
||||
]
|
||||
files = [
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"},
|
||||
{file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"},
|
||||
{file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"},
|
||||
{file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"},
|
||||
{file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"},
|
||||
{file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"},
|
||||
{file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"},
|
||||
{file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"},
|
||||
{file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"},
|
||||
{file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"},
|
||||
{file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"},
|
||||
{file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"},
|
||||
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"},
|
||||
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"},
|
||||
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"},
|
||||
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"},
|
||||
{file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"},
|
||||
{file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
|
||||
{file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1233,120 +1014,52 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "12.0"
|
||||
requires_python = ">=3.8"
|
||||
version = "14.1"
|
||||
requires_python = ">=3.9"
|
||||
summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
|
||||
{file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"},
|
||||
{file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"},
|
||||
{file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"},
|
||||
{file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"},
|
||||
{file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"},
|
||||
{file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"},
|
||||
{file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"},
|
||||
{file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"},
|
||||
{file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"},
|
||||
{file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"},
|
||||
{file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"},
|
||||
{file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"},
|
||||
{file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"},
|
||||
{file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"},
|
||||
{file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"},
|
||||
{file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"},
|
||||
{file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"},
|
||||
{file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"},
|
||||
{file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"},
|
||||
{file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"},
|
||||
{file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"},
|
||||
{file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"},
|
||||
{file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"},
|
||||
{file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"},
|
||||
{file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"},
|
||||
{file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"},
|
||||
{file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"},
|
||||
{file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"},
|
||||
{file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"},
|
||||
{file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"},
|
||||
{file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"},
|
||||
{file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"},
|
||||
{file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
|
||||
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
|
||||
{file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"},
|
||||
{file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"},
|
||||
{file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yarl"
|
||||
version = "1.9.4"
|
||||
requires_python = ">=3.7"
|
||||
version = "1.17.1"
|
||||
requires_python = ">=3.9"
|
||||
summary = "Yet another URL library"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
dependencies = [
|
||||
"idna>=2.0",
|
||||
"multidict>=4.0",
|
||||
"typing-extensions>=3.7.4; python_version < \"3.8\"",
|
||||
"propcache>=0.2.0",
|
||||
]
|
||||
files = [
|
||||
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"},
|
||||
{file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"},
|
||||
{file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"},
|
||||
{file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"},
|
||||
{file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"},
|
||||
{file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"},
|
||||
{file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3"},
|
||||
{file = "yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06"},
|
||||
{file = "yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yt-dlp"
|
||||
version = "2024.7.25"
|
||||
requires_python = ">=3.8"
|
||||
version = "2024.12.1.232904.dev0"
|
||||
requires_python = ">=3.9"
|
||||
summary = "A feature-rich command-line audio/video downloader"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
files = [
|
||||
{file = "yt_dlp-2024.12.1.232904.dev0-py3-none-any.whl", hash = "sha256:67c1fa0986662f2f26ba70789c79c6d04fda45eb530944872f4a47ef4f94047e"},
|
||||
{file = "yt_dlp-2024.12.1.232904.dev0.tar.gz", hash = "sha256:4fcce6637e70bad7c63220e4dd69a3a6b8969cbc17c93bfe569940dd173c0548"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yt-dlp"
|
||||
version = "2024.12.1.232904.dev0"
|
||||
extras = ["default"]
|
||||
requires_python = ">=3.9"
|
||||
summary = "A feature-rich command-line audio/video downloader"
|
||||
groups = ["default"]
|
||||
marker = "python_full_version == \"3.10.12\""
|
||||
@@ -1358,9 +1071,10 @@ dependencies = [
|
||||
"pycryptodomex",
|
||||
"requests<3,>=2.32.2",
|
||||
"urllib3<3,>=1.26.17",
|
||||
"websockets>=12.0",
|
||||
"websockets>=13.0",
|
||||
"yt-dlp==2024.12.1.232904.dev0",
|
||||
]
|
||||
files = [
|
||||
{file = "yt_dlp-2024.7.25-py3-none-any.whl", hash = "sha256:f44b5f33776b4f718900c670fe6e4698fb6fcd426455cd837cf25a1d6d4d9560"},
|
||||
{file = "yt_dlp-2024.7.25.tar.gz", hash = "sha256:7587aa25e236cf7b14bdb9378bbffff51202d901b04202be0cf62cbb56d3b52c"},
|
||||
{file = "yt_dlp-2024.12.1.232904.dev0-py3-none-any.whl", hash = "sha256:67c1fa0986662f2f26ba70789c79c6d04fda45eb530944872f4a47ef4f94047e"},
|
||||
{file = "yt_dlp-2024.12.1.232904.dev0.tar.gz", hash = "sha256:4fcce6637e70bad7c63220e4dd69a3a6b8969cbc17c93bfe569940dd173c0548"},
|
||||
]
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
async def code1(arg1):
|
||||
global log, xiaomusic
|
||||
log.info(f"code1:{arg1}")
|
||||
await xiaomusic.do_tts("你好,我是自定义的测试口令")
|
||||
did = xiaomusic._cur_did
|
||||
await xiaomusic.do_tts(did, "你好,我是自定义的测试口令")
|
||||
|
||||
last_record = xiaomusic.last_record
|
||||
query = last_record.get("query", "").strip()
|
||||
await xiaomusic.do_tts(did, f"你说的是: {query}")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "xiaomusic"
|
||||
version = "0.3.21"
|
||||
version = "0.3.53"
|
||||
description = "Play Music with xiaomi AI speaker"
|
||||
authors = [
|
||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||
@@ -9,13 +9,19 @@ dependencies = [
|
||||
"aiohttp>=3.8.6",
|
||||
"miservice-fork>=2.7.0",
|
||||
"mutagen>=1.47.0",
|
||||
"yt-dlp>=2024.07.01",
|
||||
"yt-dlp[default]>=2024.12.1.232904.dev0",
|
||||
"uvicorn>=0.30.1",
|
||||
"fastapi>=0.111.0",
|
||||
"fastapi>=0.115.4",
|
||||
"starlette>=0.37.2",
|
||||
"aiofiles>=24.1.0",
|
||||
"ga4mp>=2.0.4",
|
||||
"apscheduler>=3.10.4",
|
||||
"opencc-python-reimplemented==0.1.7",
|
||||
"pillow>=10.4.0",
|
||||
"python-multipart>=0.0.12",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
requires-python = ">=3.10,<3.12"
|
||||
requires-python = ">=3.10,<=3.12"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
||||
@@ -57,11 +63,18 @@ include = ["**/*.py", "**/*.pyi", "**/pyproject.toml"]
|
||||
convention = "google"
|
||||
|
||||
[tool.ruff.lint.flake8-bugbear]
|
||||
extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi.Query", "fastapi.params.Query"]
|
||||
extend-immutable-calls = [
|
||||
"fastapi.Depends",
|
||||
"fastapi.params.Depends",
|
||||
"fastapi.Query",
|
||||
"fastapi.params.Query",
|
||||
"fastapi.File"
|
||||
]
|
||||
|
||||
[tool.pdm.scripts]
|
||||
lint = "ruff check ."
|
||||
fmt = "ruff format ."
|
||||
lintfmt = {composite = ["ruff check --fix .", "ruff format ."]}
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
|
||||
33
test/test_difflib.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import difflib
|
||||
|
||||
from xiaomusic.utils import (
|
||||
find_best_match,
|
||||
keyword_detection,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
user_input = "八年的爱"
|
||||
s1 = "冰冰超人 - 八年的爱新版"
|
||||
s2 = "冰冰超人 - 八年的爱"
|
||||
r1 = difflib.SequenceMatcher(None, s1, user_input).ratio()
|
||||
r2 = difflib.SequenceMatcher(None, s2, user_input).ratio()
|
||||
print(s1, r1)
|
||||
print(s2, r2)
|
||||
|
||||
s3 = "其他"
|
||||
str_list = [s2, s1, s3]
|
||||
matches, remains = keyword_detection(user_input, str_list, n=10)
|
||||
print(matches, remains)
|
||||
|
||||
extra_search_index = {}
|
||||
extra_search_index["1"] = s1
|
||||
extra_search_index["2"] = s2
|
||||
extra_search_index["3"] = s3
|
||||
real_names = find_best_match(
|
||||
user_input,
|
||||
str_list,
|
||||
cutoff=0.4,
|
||||
n=100,
|
||||
extra_search_index=extra_search_index,
|
||||
)
|
||||
print(real_names)
|
||||
32
test/test_music_duration.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import math
|
||||
|
||||
from xiaomusic.const import (
|
||||
SUPPORT_MUSIC_TYPE,
|
||||
)
|
||||
from xiaomusic.utils import (
|
||||
get_local_music_duration,
|
||||
traverse_music_directory,
|
||||
)
|
||||
|
||||
|
||||
async def test_one_music(filename):
|
||||
# 获取播放时长
|
||||
duration = await get_local_music_duration(filename)
|
||||
sec = math.ceil(duration)
|
||||
print(f"本地歌曲 : {filename} 的时长 {duration} {sec} 秒")
|
||||
|
||||
|
||||
async def main(directory):
|
||||
# 获取所有歌曲文件
|
||||
local_musics = traverse_music_directory(directory, 10, [], SUPPORT_MUSIC_TYPE)
|
||||
print(local_musics)
|
||||
for _, files in local_musics.items():
|
||||
for file in files:
|
||||
await test_one_music(file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
directory = "./music" # 替换为你的音乐目录路径
|
||||
asyncio.run(main(directory))
|
||||
47
test/test_music_tags.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import traceback
|
||||
|
||||
from xiaomusic.const import (
|
||||
SUPPORT_MUSIC_TYPE,
|
||||
)
|
||||
from xiaomusic.utils import (
|
||||
extract_audio_metadata,
|
||||
traverse_music_directory,
|
||||
)
|
||||
|
||||
# title 标题
|
||||
# artist 艺术家
|
||||
# album 影集
|
||||
# year 年
|
||||
# genre 性
|
||||
# picture 图片
|
||||
# lyrics 歌词
|
||||
|
||||
|
||||
async def test_one_music(filename):
|
||||
# 获取播放时长
|
||||
try:
|
||||
metadata = extract_audio_metadata(filename, "cache/picture_cache")
|
||||
print(metadata)
|
||||
except Exception as e:
|
||||
print(f"歌曲 : {filename} no tag {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
async def main(directory):
|
||||
# 获取所有歌曲文件
|
||||
local_musics = traverse_music_directory(directory, 10, [], SUPPORT_MUSIC_TYPE)
|
||||
for _, files in local_musics.items():
|
||||
for file in files:
|
||||
print(file)
|
||||
# await test_one_music(file)
|
||||
pass
|
||||
|
||||
await test_one_music("music/4 In Love - 一千零一个愿望.mp3")
|
||||
# await test_one_music("./music/程响-人间烟火.flac")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
directory = "./music" # 替换为你的音乐目录路径
|
||||
asyncio.run(main(directory))
|
||||
8
test/test_remove_common_prefix.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from xiaomusic.utils import (
|
||||
remove_common_prefix,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
remove_common_prefix(
|
||||
"./tmp/【无损音质】2024年9月酷狗热歌榜TOP100合集(只选热歌最高的)首首王炸,分P合集!"
|
||||
)
|
||||
@@ -16,13 +16,12 @@ def get_html_files(directory):
|
||||
|
||||
def update_html_version(html_files, version):
|
||||
"""
|
||||
更新HTML文件中所有以 /static/ 开头的CSS和JS文件引用的版本号。
|
||||
更新HTML文件中所有以 ./ 开头的CSS和JS文件引用的版本号。
|
||||
|
||||
:param html_files: 需要更新的HTML文件路径的列表。
|
||||
:param version: 新的版本号字符串。
|
||||
"""
|
||||
pattern = re.compile(r'(/static/.*(css|js))\?version=[^"]*"')
|
||||
# pattern = re.compile(r'(/static/.*html)\?version=[^"]*"')
|
||||
pattern = re.compile(r'(\./.*(css|js))\?version=[^"]*"')
|
||||
|
||||
for html_file in html_files:
|
||||
if not html_file.exists():
|
||||
@@ -48,7 +47,7 @@ if __name__ == "__main__":
|
||||
t = str(int(time.time()))
|
||||
|
||||
# 指定目录
|
||||
html_directory = "xiaomusic/static" # 修改为实际的HTML文件目录路径
|
||||
html_directory = "xiaomusic/static/default" # 修改为实际的HTML文件目录路径
|
||||
|
||||
# 获取HTML文件列表
|
||||
html_files_to_update = get_html_files(html_directory)
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.3.21"
|
||||
__version__ = "0.3.53"
|
||||
|
||||
84
xiaomusic/analytics.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
from ga4mp import GtagMP
|
||||
|
||||
from xiaomusic import __version__
|
||||
|
||||
|
||||
class Analytics:
|
||||
def __init__(self, log):
|
||||
self.gtag = None
|
||||
self.current_date = None
|
||||
self.log = log
|
||||
self.init()
|
||||
|
||||
def init(self):
|
||||
if self.gtag is not None:
|
||||
return
|
||||
|
||||
gtag = GtagMP(
|
||||
api_secret="sVRsf3T9StuWc-ZiWZxDVA",
|
||||
measurement_id="G-Z09NC1K7ZW",
|
||||
client_id="",
|
||||
)
|
||||
gtag.client_id = gtag.random_client_id()
|
||||
gtag.store.set_user_property(name="version", value=__version__)
|
||||
self.gtag = gtag
|
||||
self.log.info("analytics init ok")
|
||||
|
||||
async def run_with_cancel(self, func, *args, **kwargs):
|
||||
try:
|
||||
asyncio.ensure_future(asyncio.to_thread(func, *args, **kwargs))
|
||||
self.log.info("analytics run_with_cancel success")
|
||||
except Exception as e:
|
||||
self.log.warning(f"analytics run_with_cancel failed {e}")
|
||||
return None
|
||||
|
||||
async def send_startup_event(self):
|
||||
try:
|
||||
await self.run_with_cancel(self._send_startup_event)
|
||||
except Exception as e:
|
||||
self.log.warning(f"analytics send_startup_event failed {e}")
|
||||
self.init()
|
||||
|
||||
def _send_startup_event(self):
|
||||
event = self.gtag.create_new_event(name="startup")
|
||||
event.set_event_param(name="version", value=__version__)
|
||||
event_list = [event]
|
||||
self.gtag.send(events=event_list)
|
||||
|
||||
async def send_daily_event(self):
|
||||
try:
|
||||
await self.run_with_cancel(self._send_daily_event)
|
||||
except Exception as e:
|
||||
self.log.warning(f"analytics send_daily_event failed {e}")
|
||||
self.init()
|
||||
|
||||
def _send_daily_event(self):
|
||||
current_date = datetime.now().strftime("%Y-%m-%d")
|
||||
if self.current_date == current_date:
|
||||
return
|
||||
|
||||
event = self.gtag.create_new_event(name="daily_active_user")
|
||||
event.set_event_param(name="version", value=__version__)
|
||||
event.set_event_param(name="date", value=current_date)
|
||||
event_list = [event]
|
||||
self.gtag.send(events=event_list)
|
||||
self.current_date = current_date
|
||||
|
||||
async def send_play_event(self, name, sec, hardware):
|
||||
try:
|
||||
await self.run_with_cancel(self._send_play_event, name, sec, hardware)
|
||||
except Exception as e:
|
||||
self.log.warning(f"analytics send_play_event failed {e}")
|
||||
self.init()
|
||||
|
||||
def _send_play_event(self, name, sec, hardware):
|
||||
event = self.gtag.create_new_event(name="play")
|
||||
event.set_event_param(name="version", value=__version__)
|
||||
event.set_event_param(name="music", value=name)
|
||||
event.set_event_param(name="sec", value=sec)
|
||||
event.set_event_param(name="hardware", value=hardware)
|
||||
event_list = [event]
|
||||
self.gtag.send(events=event_list)
|
||||
@@ -3,7 +3,6 @@ import argparse
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
import uvicorn
|
||||
|
||||
@@ -137,45 +136,31 @@ def main():
|
||||
|
||||
try:
|
||||
filename = config.getsettingfile()
|
||||
with open(filename) as f:
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
data = json.loads(f.read())
|
||||
config.update_config(data)
|
||||
except Exception as e:
|
||||
print(f"Execption {e}")
|
||||
|
||||
def run_server():
|
||||
def run_server(port):
|
||||
xiaomusic = XiaoMusic(config)
|
||||
HttpInit(xiaomusic)
|
||||
uvicorn.run(
|
||||
HttpApp,
|
||||
host="127.0.0.1",
|
||||
port=config.port + 1,
|
||||
host=["0.0.0.0", "::"],
|
||||
port=port,
|
||||
log_config=LOGGING_CONFIG,
|
||||
)
|
||||
|
||||
command = [
|
||||
"uvicorn",
|
||||
"xiaomusic.gate:app",
|
||||
"--workers",
|
||||
"4",
|
||||
"--host",
|
||||
"0.0.0.0",
|
||||
"--port",
|
||||
str(config.port),
|
||||
]
|
||||
|
||||
process = subprocess.Popen(command)
|
||||
def signal_handler(sig, frame):
|
||||
print("主进程收到退出信号,准备退出...")
|
||||
process.terminate() # 终止子进程
|
||||
process.wait() # 等待子进程退出
|
||||
print("子进程已退出")
|
||||
os._exit(0) # 退出主进程
|
||||
|
||||
# 捕获主进程的退出信号
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
run_server()
|
||||
port = int(config.port)
|
||||
run_server(port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,22 +6,32 @@ import os
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from typing import get_type_hints
|
||||
|
||||
from xiaomusic.const import (
|
||||
PLAY_TYPE_ALL,
|
||||
PLAY_TYPE_ONE,
|
||||
PLAY_TYPE_RND,
|
||||
PLAY_TYPE_SEQ,
|
||||
PLAY_TYPE_SIN,
|
||||
)
|
||||
from xiaomusic.utils import validate_proxy
|
||||
|
||||
|
||||
# 默认口令
|
||||
def default_key_word_dict():
|
||||
return {
|
||||
"播放歌曲": "play",
|
||||
"播放本地歌曲": "playlocal",
|
||||
"关机": "stop",
|
||||
"下一首": "play_next",
|
||||
"上一首": "play_prev",
|
||||
"单曲循环": "set_play_type_one",
|
||||
"全部循环": "set_play_type_all",
|
||||
"随机播放": "set_random_play",
|
||||
"随机播放": "set_play_type_rnd",
|
||||
"单曲播放": "set_play_type_sin",
|
||||
"顺序播放": "set_play_type_seq",
|
||||
"分钟后关机": "stop_after_minute",
|
||||
"播放列表": "play_music_list",
|
||||
"刷新列表": "gen_music_list",
|
||||
"加入收藏": "add_to_favorites",
|
||||
"收藏歌曲": "add_to_favorites",
|
||||
"取消收藏": "del_from_favorites",
|
||||
"播放列表第": "play_music_list_index",
|
||||
}
|
||||
|
||||
|
||||
@@ -42,14 +52,20 @@ KEY_WORD_ARG_BEFORE_DICT = {
|
||||
def default_key_match_order():
|
||||
return [
|
||||
"分钟后关机",
|
||||
"播放歌曲",
|
||||
"下一首",
|
||||
"上一首",
|
||||
"单曲循环",
|
||||
"全部循环",
|
||||
"随机播放",
|
||||
"单曲播放",
|
||||
"顺序播放",
|
||||
"关机",
|
||||
"刷新列表",
|
||||
"播放列表第",
|
||||
"播放列表",
|
||||
"加入收藏",
|
||||
"收藏歌曲",
|
||||
"取消收藏",
|
||||
]
|
||||
|
||||
|
||||
@@ -69,13 +85,14 @@ class Config:
|
||||
account: str = os.getenv("MI_USER", "")
|
||||
password: str = os.getenv("MI_PASS", "")
|
||||
mi_did: str = os.getenv("MI_DID", "") # 逗号分割支持多设备
|
||||
miio_tts_command: str = os.getenv("MIIO_TTS_CMD", "")
|
||||
cookie: str = ""
|
||||
verbose: bool = os.getenv("XIAOMUSIC_VERBOSE", "").lower() == "true"
|
||||
music_path: str = os.getenv(
|
||||
"XIAOMUSIC_MUSIC_PATH", "music"
|
||||
) # 只能是music目录下的子目录
|
||||
download_path: str = os.getenv("XIAOMUSIC_DOWNLOAD_PATH", "")
|
||||
music_path: str = os.getenv("XIAOMUSIC_MUSIC_PATH", "music")
|
||||
temp_path: str = os.getenv("XIAOMUSIC_TEMP_PATH", "music/tmp")
|
||||
download_path: str = os.getenv("XIAOMUSIC_DOWNLOAD_PATH", "music/download")
|
||||
conf_path: str = os.getenv("XIAOMUSIC_CONF_PATH", "conf")
|
||||
cache_dir: str = os.getenv("XIAOMUSIC_CACHE_DIR", "cache")
|
||||
hostname: str = os.getenv("XIAOMUSIC_HOSTNAME", "192.168.2.5")
|
||||
port: int = int(os.getenv("XIAOMUSIC_PORT", "8090")) # 监听端口
|
||||
public_port: int = int(os.getenv("XIAOMUSIC_PUBLIC_PORT", 0)) # 歌曲访问端口
|
||||
@@ -85,9 +102,10 @@ class Config:
|
||||
) # "bilisearch:" or "ytsearch:"
|
||||
ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
|
||||
active_cmd: str = os.getenv(
|
||||
"XIAOMUSIC_ACTIVE_CMD", "play,set_random_play,playlocal,play_music_list,stop"
|
||||
"XIAOMUSIC_ACTIVE_CMD",
|
||||
"play,set_play_type_rnd,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop",
|
||||
)
|
||||
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir")
|
||||
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir,tmp")
|
||||
music_path_depth: int = int(os.getenv("XIAOMUSIC_MUSIC_PATH_DEPTH", "10"))
|
||||
disable_httpauth: bool = (
|
||||
os.getenv("XIAOMUSIC_DISABLE_HTTPAUTH", "true").lower() == "true"
|
||||
@@ -96,6 +114,7 @@ class Config:
|
||||
httpauth_password: str = os.getenv("XIAOMUSIC_HTTPAUTH_PASSWORD", "")
|
||||
music_list_url: str = os.getenv("XIAOMUSIC_MUSIC_LIST_URL", "")
|
||||
music_list_json: str = os.getenv("XIAOMUSIC_MUSIC_LIST_JSON", "")
|
||||
custom_play_list_json: str = os.getenv("XIAOMUSIC_CUSTOM_PLAY_LIST_JSON", "")
|
||||
disable_download: bool = (
|
||||
os.getenv("XIAOMUSIC_DISABLE_DOWNLOAD", "false").lower() == "true"
|
||||
)
|
||||
@@ -108,7 +127,7 @@ class Config:
|
||||
"XIAOMUSIC_USE_MUSIC_AUDIO_ID", "1582971365183456177"
|
||||
)
|
||||
use_music_id: str = os.getenv("XIAOMUSIC_USE_MUSIC_ID", "355454500")
|
||||
log_file: str = os.getenv("XIAOMUSIC_LOG_FILE", "/tmp/xiaomusic.txt")
|
||||
log_file: str = os.getenv("XIAOMUSIC_LOG_FILE", "xiaomusic.log.txt")
|
||||
# 模糊搜索匹配的最低相似度阈值
|
||||
fuzzy_match_cutoff: float = float(os.getenv("XIAOMUSIC_FUZZY_MATCH_CUTOFF", "0.6"))
|
||||
# 开启模糊搜索
|
||||
@@ -123,6 +142,9 @@ class Config:
|
||||
)
|
||||
keywords_play: str = os.getenv("XIAOMUSIC_KEYWORDS_PLAY", "播放歌曲,放歌曲")
|
||||
keywords_stop: str = os.getenv("XIAOMUSIC_KEYWORDS_STOP", "关机,暂停,停止,停止播放")
|
||||
keywords_playlist: str = os.getenv(
|
||||
"XIAOMUSIC_KEYWORDS_PLAYLIST", "播放列表,播放歌单"
|
||||
)
|
||||
user_key_word_dict: dict[str, str] = field(
|
||||
default_factory=default_user_key_word_dict
|
||||
)
|
||||
@@ -136,13 +158,41 @@ class Config:
|
||||
remove_id3tag: bool = (
|
||||
os.getenv("XIAOMUSIC_REMOVE_ID3TAG", "false").lower() == "true"
|
||||
)
|
||||
convert_to_mp3: bool = os.getenv("CONVERT_TO_MP3", "false").lower() == "true"
|
||||
delay_sec: int = int(os.getenv("XIAOMUSIC_DELAY_SEC", 3)) # 下一首歌延迟播放秒数
|
||||
continue_play: bool = (
|
||||
os.getenv("XIAOMUSIC_CONTINUE_PLAY", "false").lower() == "true"
|
||||
)
|
||||
pull_ask_sec: int = int(os.getenv("XIAOMUSIC_PULL_ASK_SEC", "1"))
|
||||
crontab_json: str = os.getenv("XIAOMUSIC_CRONTAB_JSON", "") # 定时任务
|
||||
enable_yt_dlp_cookies: bool = (
|
||||
os.getenv("XIAOMUSIC_ENABLE_YT_DLP_COOKIES", "false").lower() == "true"
|
||||
)
|
||||
get_ask_by_mina: bool = (
|
||||
os.getenv("XIAOMUSIC_GET_ASK_BY_MINA", "false").lower() == "true"
|
||||
)
|
||||
play_type_one_tts_msg: str = os.getenv(
|
||||
"XIAOMUSIC_PLAY_TYPE_ONE_TTS_MSG", "已经设置为单曲循环"
|
||||
)
|
||||
play_type_all_tts_msg: str = os.getenv(
|
||||
"XIAOMUSIC_PLAY_TYPE_ALL_TTS_MSG", "已经设置为全部循环"
|
||||
)
|
||||
play_type_rnd_tts_msg: str = os.getenv(
|
||||
"XIAOMUSIC_PLAY_TYPE_RND_TTS_MSG", "已经设置为随机播放"
|
||||
)
|
||||
play_type_sin_tts_msg: str = os.getenv(
|
||||
"XIAOMUSIC_PLAY_TYPE_SIN_TTS_MSG", "已经设置为单曲播放"
|
||||
)
|
||||
play_type_seq_tts_msg: str = os.getenv(
|
||||
"XIAOMUSIC_PLAY_TYPE_SEQ_TTS_MSG", "已经设置为顺序播放"
|
||||
)
|
||||
|
||||
def append_keyword(self, keys, action):
|
||||
for key in keys.split(","):
|
||||
self.key_word_dict[key] = action
|
||||
if key not in self.key_match_order:
|
||||
self.key_match_order.append(key)
|
||||
if key:
|
||||
self.key_word_dict[key] = action
|
||||
if key not in self.key_match_order:
|
||||
self.key_match_order.append(key)
|
||||
|
||||
def append_user_keyword(self):
|
||||
for k, v in self.user_key_word_dict.items():
|
||||
@@ -152,10 +202,15 @@ class Config:
|
||||
|
||||
def init_keyword(self):
|
||||
self.key_match_order = default_key_match_order()
|
||||
self.key_word_dict = default_key_word_dict()
|
||||
self.append_keyword(self.keywords_playlocal, "playlocal")
|
||||
self.append_keyword(self.keywords_play, "play")
|
||||
self.append_keyword(self.keywords_stop, "stop")
|
||||
self.append_keyword(self.keywords_playlist, "play_music_list")
|
||||
self.append_user_keyword()
|
||||
self.key_match_order = [
|
||||
x for x in self.key_match_order if x in self.key_word_dict
|
||||
]
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.proxy:
|
||||
@@ -229,3 +284,43 @@ class Config:
|
||||
os.makedirs(self.conf_path)
|
||||
filename = os.path.join(self.conf_path, "setting.json")
|
||||
return filename
|
||||
|
||||
@property
|
||||
def tag_cache_path(self):
|
||||
if not os.path.exists(self.cache_dir):
|
||||
os.makedirs(self.cache_dir)
|
||||
filename = os.path.join(self.cache_dir, "tag_cache.json")
|
||||
return filename
|
||||
|
||||
@property
|
||||
def picture_cache_path(self):
|
||||
cache_path = os.path.join(self.cache_dir, "picture_cache")
|
||||
if not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
return cache_path
|
||||
|
||||
@property
|
||||
def yt_dlp_cookies_path(self):
|
||||
if not os.path.exists(self.conf_path):
|
||||
os.makedirs(self.conf_path)
|
||||
cookies_path = os.path.join(self.conf_path, "yt-dlp-cookie.txt")
|
||||
return cookies_path
|
||||
|
||||
@property
|
||||
def temp_dir(self):
|
||||
if not os.path.exists(self.temp_path):
|
||||
os.makedirs(self.temp_path)
|
||||
return self.temp_path
|
||||
|
||||
def get_play_type_tts(self, play_type):
|
||||
if play_type == PLAY_TYPE_ONE:
|
||||
return self.play_type_one_tts_msg
|
||||
if play_type == PLAY_TYPE_ALL:
|
||||
return self.play_type_all_tts_msg
|
||||
if play_type == PLAY_TYPE_RND:
|
||||
return self.play_type_rnd_tts_msg
|
||||
if play_type == PLAY_TYPE_SIN:
|
||||
return self.play_type_sin_tts_msg
|
||||
if play_type == PLAY_TYPE_SEQ:
|
||||
return self.play_type_seq_tts_msg
|
||||
return ""
|
||||
|
||||
@@ -13,9 +13,10 @@ COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={u
|
||||
PLAY_TYPE_ONE = 0 # 单曲循环
|
||||
PLAY_TYPE_ALL = 1 # 全部循环
|
||||
PLAY_TYPE_RND = 2 # 随机播放
|
||||
PLAY_TYPE_SIN = 3 # 单曲播放
|
||||
PLAY_TYPE_SEQ = 4 # 顺序播放
|
||||
|
||||
PLAY_TYPE_TTS = {
|
||||
PLAY_TYPE_ONE: "已经设置为单曲循环",
|
||||
PLAY_TYPE_ALL: "已经设置为全部循环",
|
||||
PLAY_TYPE_RND: "已经设置为随机播放",
|
||||
# 需要采用 mina 获取对话记录的设备型号
|
||||
GET_ASK_BY_MINA = {
|
||||
"M01",
|
||||
}
|
||||
|
||||
113
xiaomusic/crontab.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import json
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
|
||||
class Crontab:
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
self.scheduler = AsyncIOScheduler()
|
||||
|
||||
def start(self):
|
||||
self.scheduler.start()
|
||||
|
||||
def add_job(self, expression, job):
|
||||
try:
|
||||
trigger = CronTrigger.from_crontab(expression)
|
||||
self.scheduler.add_job(job, trigger)
|
||||
except ValueError as e:
|
||||
self.log.error(f"Invalid crontab expression {e}")
|
||||
except Exception as e:
|
||||
self.log.exception(f"Execption {e}")
|
||||
|
||||
# 添加关机任务
|
||||
def add_job_stop(self, expression, xiaomusic, did, **kwargs):
|
||||
async def job():
|
||||
await xiaomusic.stop(did, "notts")
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
# 添加播放任务
|
||||
def add_job_play(self, expression, xiaomusic, did, arg1, **kwargs):
|
||||
async def job():
|
||||
await xiaomusic.play(did, arg1)
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
# 添加播放列表任务
|
||||
def add_job_play_music_list(self, expression, xiaomusic, did, arg1, **kwargs):
|
||||
async def job():
|
||||
await xiaomusic.play_music_list(did, arg1)
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
# 添加语音播放任务
|
||||
def add_job_tts(self, expression, xiaomusic, did, arg1, **kwargs):
|
||||
async def job():
|
||||
await xiaomusic.do_tts(did, arg1)
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
# 刷新播放列表任务
|
||||
def add_job_refresh_music_list(self, expression, xiaomusic, **kwargs):
|
||||
async def job():
|
||||
await xiaomusic.gen_music_list()
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
# 设置音量任务
|
||||
def add_job_set_volume(self, expression, xiaomusic, did, arg1, **kwargs):
|
||||
async def job():
|
||||
await xiaomusic.set_volume(did, arg1)
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
# 设置播放类型任务
|
||||
def add_job_set_play_type(self, expression, xiaomusic, did, arg1, **kwargs):
|
||||
async def job():
|
||||
play_type = int(arg1)
|
||||
await xiaomusic.set_play_type(did, play_type, False)
|
||||
|
||||
self.add_job(expression, job)
|
||||
|
||||
def add_job_cron(self, xiaomusic, cron):
|
||||
expression = cron["expression"] # cron 计划格式
|
||||
name = cron["name"] # stop, play, play_music_list, tts
|
||||
did = cron.get("did", "")
|
||||
arg1 = cron.get("arg1", "")
|
||||
jobname = f"add_job_{name}"
|
||||
func = getattr(self, jobname, None)
|
||||
if callable(func):
|
||||
func(expression, xiaomusic, did=did, arg1=arg1)
|
||||
self.log.info(
|
||||
f"crontab add_job_cron ok. did:{did}, name:{name}, arg1:{arg1}"
|
||||
)
|
||||
else:
|
||||
self.log.error(
|
||||
f"'{self.__class__.__name__}' object has no attribute '{jobname}'"
|
||||
)
|
||||
|
||||
# 清空任务
|
||||
def clear_jobs(self):
|
||||
for job in self.scheduler.get_jobs():
|
||||
try:
|
||||
job.remove()
|
||||
except Exception as e:
|
||||
self.log.exception(f"Execption {e}")
|
||||
|
||||
# 重新加载计划任务
|
||||
def reload_config(self, xiaomusic):
|
||||
self.clear_jobs()
|
||||
|
||||
crontab_json = xiaomusic.config.crontab_json
|
||||
if not crontab_json:
|
||||
return
|
||||
|
||||
try:
|
||||
cron_list = json.loads(crontab_json)
|
||||
for cron in cron_list:
|
||||
self.add_job_cron(xiaomusic, cron)
|
||||
self.log.info("crontab reload_config ok")
|
||||
except Exception as e:
|
||||
self.log.exception(f"Execption {e}")
|
||||
@@ -1,79 +0,0 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from xiaomusic import __version__
|
||||
from xiaomusic.config import Config
|
||||
|
||||
config = Config()
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(app):
|
||||
global config
|
||||
try:
|
||||
filename = config.getsettingfile()
|
||||
with open(filename) as f:
|
||||
data = json.loads(f.read())
|
||||
config.update_config(data)
|
||||
except Exception as e:
|
||||
log.exception(f"Execption {e}")
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
lifespan=app_lifespan,
|
||||
version=__version__,
|
||||
)
|
||||
|
||||
|
||||
def reset_gate():
|
||||
# 更新 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",
|
||||
)
|
||||
|
||||
|
||||
folder = os.path.dirname(__file__)
|
||||
app.mount("/static", StaticFiles(directory=f"{folder}/static"), name="static")
|
||||
reset_gate()
|
||||
|
||||
|
||||
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
|
||||
async def proxy(path: str, request: Request):
|
||||
async with httpx.AsyncClient() as client:
|
||||
port = config.port + 1
|
||||
url = f"http://127.0.0.1:{port}/{path}"
|
||||
response = await client.request(
|
||||
method=request.method,
|
||||
url=url,
|
||||
headers=request.headers,
|
||||
params=request.query_params,
|
||||
content=await request.body() if request.method in ["POST", "PUT"] else None,
|
||||
)
|
||||
if path == "savesetting":
|
||||
# 使用BackgroundTask在响应发送完毕后执行逻辑
|
||||
background_task = BackgroundTask(reset_gate)
|
||||
return Response(
|
||||
content=response.content,
|
||||
status_code=response.status_code,
|
||||
headers=dict(response.headers),
|
||||
background=background_task,
|
||||
)
|
||||
return Response(
|
||||
content=response.content,
|
||||
status_code=response.status_code,
|
||||
headers=dict(response.headers),
|
||||
)
|
||||
@@ -1,24 +1,49 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import asdict
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
||||
import aiofiles
|
||||
from fastapi import (
|
||||
Depends,
|
||||
FastAPI,
|
||||
File,
|
||||
HTTPException,
|
||||
Query,
|
||||
Request,
|
||||
UploadFile,
|
||||
status,
|
||||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from fastapi.responses import RedirectResponse
|
||||
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 starlette.responses import FileResponse, Response
|
||||
|
||||
from xiaomusic import __version__
|
||||
from xiaomusic.utils import (
|
||||
convert_file_to_mp3,
|
||||
deepcopy_data_no_sensitive_info,
|
||||
download_one_music,
|
||||
download_playlist,
|
||||
downloadfile,
|
||||
get_latest_version,
|
||||
is_mp3,
|
||||
remove_common_prefix,
|
||||
remove_id3_tags,
|
||||
try_add_access_control_param,
|
||||
)
|
||||
|
||||
xiaomusic = None
|
||||
@@ -68,7 +93,17 @@ def no_verification():
|
||||
app = FastAPI(
|
||||
lifespan=app_lifespan,
|
||||
version=__version__,
|
||||
dependencies=[Depends(verification)],
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
openapi_url=None,
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # 允许访问的源
|
||||
allow_credentials=False, # 支持 cookie
|
||||
allow_methods=["*"], # 允许使用的请求方法
|
||||
allow_headers=["*"], # 允许携带的 Headers
|
||||
)
|
||||
|
||||
|
||||
@@ -79,13 +114,16 @@ 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",
|
||||
)
|
||||
|
||||
class AuthStaticFiles(StaticFiles):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def __call__(self, scope, receive, send) -> None:
|
||||
request = Request(scope, receive)
|
||||
if not config.disable_httpauth:
|
||||
assert verification(await security(request))
|
||||
await super().__call__(scope, receive, send)
|
||||
|
||||
|
||||
def HttpInit(_xiaomusic):
|
||||
@@ -95,24 +133,24 @@ def HttpInit(_xiaomusic):
|
||||
log = xiaomusic.log
|
||||
|
||||
folder = os.path.dirname(__file__)
|
||||
app.mount("/static", StaticFiles(directory=f"{folder}/static"), name="static")
|
||||
app.mount("/static", AuthStaticFiles(directory=f"{folder}/static"), name="static")
|
||||
reset_http_server()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def read_index():
|
||||
async def read_index(Verifcation=Depends(verification)):
|
||||
folder = os.path.dirname(__file__)
|
||||
return FileResponse(f"{folder}/static/index.html")
|
||||
|
||||
|
||||
@app.get("/getversion")
|
||||
def getversion():
|
||||
def getversion(Verifcation=Depends(verification)):
|
||||
log.debug("getversion %s", __version__)
|
||||
return {"version": __version__}
|
||||
|
||||
|
||||
@app.get("/getvolume")
|
||||
async def getvolume(did: str = ""):
|
||||
async def getvolume(did: str = "", Verifcation=Depends(verification)):
|
||||
if not xiaomusic.did_exist(did):
|
||||
return {"volume": 0}
|
||||
|
||||
@@ -126,7 +164,7 @@ class DidVolume(BaseModel):
|
||||
|
||||
|
||||
@app.post("/setvolume")
|
||||
async def setvolume(data: DidVolume):
|
||||
async def setvolume(data: DidVolume, Verifcation=Depends(verification)):
|
||||
did = data.did
|
||||
volume = data.volume
|
||||
if not xiaomusic.did_exist(did):
|
||||
@@ -138,21 +176,27 @@ async def setvolume(data: DidVolume):
|
||||
|
||||
|
||||
@app.get("/searchmusic")
|
||||
def searchmusic(name: str = ""):
|
||||
def searchmusic(name: str = "", Verifcation=Depends(verification)):
|
||||
return xiaomusic.searchmusic(name)
|
||||
|
||||
|
||||
@app.get("/playingmusic")
|
||||
def playingmusic(did: str = ""):
|
||||
def playingmusic(did: str = "", Verifcation=Depends(verification)):
|
||||
if not xiaomusic.did_exist(did):
|
||||
return {"ret": "Did not exist"}
|
||||
|
||||
is_playing = xiaomusic.isplaying(did)
|
||||
cur_music = xiaomusic.playingmusic(did)
|
||||
cur_playlist = xiaomusic.get_cur_play_list(did)
|
||||
# 播放进度
|
||||
offset, duration = xiaomusic.get_offset_duration(did)
|
||||
return {
|
||||
"ret": "OK",
|
||||
"is_playing": is_playing,
|
||||
"cur_music": cur_music,
|
||||
"cur_playlist": cur_playlist,
|
||||
"offset": offset,
|
||||
"duration": duration,
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +206,7 @@ class DidCmd(BaseModel):
|
||||
|
||||
|
||||
@app.post("/cmd")
|
||||
async def do_cmd(data: DidCmd):
|
||||
async def do_cmd(data: DidCmd, Verifcation=Depends(verification)):
|
||||
did = data.did
|
||||
cmd = data.cmd
|
||||
log.info(f"docmd. did:{did} cmd:{cmd}")
|
||||
@@ -170,17 +214,30 @@ async def do_cmd(data: DidCmd):
|
||||
return {"ret": "Did not exist"}
|
||||
|
||||
if len(cmd) > 0:
|
||||
await xiaomusic.cancel_all_tasks()
|
||||
task = asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
|
||||
xiaomusic.append_running_task(task)
|
||||
try:
|
||||
await xiaomusic.cancel_all_tasks()
|
||||
task = asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
|
||||
xiaomusic.append_running_task(task)
|
||||
except Exception as e:
|
||||
log.warning(f"Execption {e}")
|
||||
return {"ret": "OK"}
|
||||
return {"ret": "Unknow cmd"}
|
||||
|
||||
|
||||
@app.get("/cmdstatus")
|
||||
async def cmd_status(Verifcation=Depends(verification)):
|
||||
finish = await xiaomusic.is_task_finish()
|
||||
if finish:
|
||||
return {"ret": "OK", "status": "finish"}
|
||||
return {"ret": "OK", "status": "running"}
|
||||
|
||||
|
||||
@app.get("/getsetting")
|
||||
async def getsetting(need_device_list: bool = False):
|
||||
async def getsetting(need_device_list: bool = False, Verifcation=Depends(verification)):
|
||||
config = xiaomusic.getconfig()
|
||||
data = asdict(config)
|
||||
data["password"] = "******"
|
||||
data["httpauth_password"] = "******"
|
||||
if need_device_list:
|
||||
device_list = await xiaomusic.getalldevices()
|
||||
log.info(f"getsetting device_list: {device_list}")
|
||||
@@ -189,12 +246,17 @@ async def getsetting(need_device_list: bool = False):
|
||||
|
||||
|
||||
@app.post("/savesetting")
|
||||
async def savesetting(request: Request):
|
||||
async def savesetting(request: Request, Verifcation=Depends(verification)):
|
||||
try:
|
||||
data_json = await request.body()
|
||||
data = json.loads(data_json.decode("utf-8"))
|
||||
debug_data = deepcopy_data_no_sensitive_info(data)
|
||||
log.info(f"saveconfig: {debug_data}")
|
||||
config = xiaomusic.getconfig()
|
||||
if data["password"] == "******" or data["password"] == "":
|
||||
data["password"] = config.password
|
||||
if data["httpauth_password"] == "******" or data["httpauth_password"] == "":
|
||||
data["httpauth_password"] = config.httpauth_password
|
||||
await xiaomusic.saveconfig(data)
|
||||
reset_http_server()
|
||||
return "save success"
|
||||
@@ -207,8 +269,59 @@ async def musiclist(Verifcation=Depends(verification)):
|
||||
return xiaomusic.get_music_list()
|
||||
|
||||
|
||||
@app.get("/musicinfo")
|
||||
async def musicinfo(
|
||||
name: str, musictag: bool = False, Verifcation=Depends(verification)
|
||||
):
|
||||
url = xiaomusic.get_music_url(name)
|
||||
info = {
|
||||
"ret": "OK",
|
||||
"name": name,
|
||||
"url": url,
|
||||
}
|
||||
if musictag:
|
||||
info["tags"] = xiaomusic.get_music_tags(name)
|
||||
return info
|
||||
|
||||
|
||||
@app.get("/musicinfos")
|
||||
async def musicinfos(
|
||||
name: list[str] = Query(None),
|
||||
musictag: bool = False,
|
||||
Verifcation=Depends(verification),
|
||||
):
|
||||
ret = []
|
||||
for music_name in name:
|
||||
url = xiaomusic.get_music_url(music_name)
|
||||
info = {
|
||||
"name": music_name,
|
||||
"url": url,
|
||||
}
|
||||
if musictag:
|
||||
info["tags"] = xiaomusic.get_music_tags(music_name)
|
||||
ret.append(info)
|
||||
return ret
|
||||
|
||||
|
||||
class MusicInfoObj(BaseModel):
|
||||
musicname: str
|
||||
title: str = ""
|
||||
artist: str = ""
|
||||
album: str = ""
|
||||
year: str = ""
|
||||
genre: str = ""
|
||||
lyrics: str = ""
|
||||
picture: str = "" # base64
|
||||
|
||||
|
||||
@app.post("/setmusictag")
|
||||
async def setmusictag(info: MusicInfoObj, Verifcation=Depends(verification)):
|
||||
ret = xiaomusic.set_music_tag(info.musicname, info)
|
||||
return {"ret": ret}
|
||||
|
||||
|
||||
@app.get("/curplaylist")
|
||||
async def curplaylist(did: str = ""):
|
||||
async def curplaylist(did: str = "", Verifcation=Depends(verification)):
|
||||
if not xiaomusic.did_exist(did):
|
||||
return ""
|
||||
return xiaomusic.get_cur_play_list(did)
|
||||
@@ -219,9 +332,9 @@ class MusicItem(BaseModel):
|
||||
|
||||
|
||||
@app.post("/delmusic")
|
||||
def delmusic(data: MusicItem):
|
||||
async def delmusic(data: MusicItem, Verifcation=Depends(verification)):
|
||||
log.info(data)
|
||||
xiaomusic.del_music(data.name)
|
||||
await xiaomusic.del_music(data.name)
|
||||
return "success"
|
||||
|
||||
|
||||
@@ -229,8 +342,46 @@ class UrlInfo(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
class DidPlayMusic(BaseModel):
|
||||
did: str
|
||||
musicname: str = ""
|
||||
searchkey: str = ""
|
||||
|
||||
|
||||
@app.post("/playmusic")
|
||||
async def playmusic(data: DidPlayMusic, Verifcation=Depends(verification)):
|
||||
did = data.did
|
||||
musicname = data.musicname
|
||||
searchkey = data.searchkey
|
||||
if not xiaomusic.did_exist(did):
|
||||
return {"ret": "Did not exist"}
|
||||
|
||||
log.info(f"playmusic {did} musicname:{musicname} searchkey:{searchkey}")
|
||||
await xiaomusic.do_play(did, musicname, searchkey)
|
||||
return {"ret": "OK"}
|
||||
|
||||
|
||||
class DidPlayMusicList(BaseModel):
|
||||
did: str
|
||||
listname: str = ""
|
||||
musicname: str = ""
|
||||
|
||||
|
||||
@app.post("/playmusiclist")
|
||||
async def playmusiclist(data: DidPlayMusicList, Verifcation=Depends(verification)):
|
||||
did = data.did
|
||||
listname = data.listname
|
||||
musicname = data.musicname
|
||||
if not xiaomusic.did_exist(did):
|
||||
return {"ret": "Did not exist"}
|
||||
|
||||
log.info(f"playmusiclist {did} listname:{listname} musicname:{musicname}")
|
||||
await xiaomusic.do_play_music_list(did, listname, musicname)
|
||||
return {"ret": "OK"}
|
||||
|
||||
|
||||
@app.post("/downloadjson")
|
||||
async def downloadjson(data: UrlInfo):
|
||||
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
|
||||
log.info(data)
|
||||
url = data.url
|
||||
content = ""
|
||||
@@ -278,16 +429,24 @@ def downloadlog(Verifcation=Depends(verification)):
|
||||
|
||||
|
||||
@app.get("/playurl")
|
||||
async def playurl(did: str, url: str):
|
||||
async def playurl(did: str, url: str, Verifcation=Depends(verification)):
|
||||
if not xiaomusic.did_exist(did):
|
||||
return {"ret": "Did not exist"}
|
||||
decoded_url = urllib.parse.unquote(url)
|
||||
log.info(f"playurl did: {did} url: {decoded_url}")
|
||||
return await xiaomusic.play_url(did=did, arg1=decoded_url)
|
||||
|
||||
log.info(f"playurl did: {did} url: {url}")
|
||||
return await xiaomusic.play_url(did=did, arg1=url)
|
||||
|
||||
@app.post("/refreshmusictag")
|
||||
async def refreshmusictag(Verifcation=Depends(verification)):
|
||||
xiaomusic.refresh_music_tag()
|
||||
return {
|
||||
"ret": "OK",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/debug_play_by_music_url")
|
||||
async def debug_play_by_music_url(request: Request):
|
||||
async def debug_play_by_music_url(request: Request, Verifcation=Depends(verification)):
|
||||
try:
|
||||
data = await request.body()
|
||||
data_dict = json.loads(data.decode("utf-8"))
|
||||
@@ -295,3 +454,247 @@ async def debug_play_by_music_url(request: Request):
|
||||
return await xiaomusic.debug_play_by_music_url(arg1=data_dict)
|
||||
except json.JSONDecodeError as err:
|
||||
raise HTTPException(status_code=400, detail="Invalid JSON") from err
|
||||
|
||||
|
||||
@app.get("/latestversion")
|
||||
async def latest_version(Verifcation=Depends(verification)):
|
||||
version = await get_latest_version("xiaomusic")
|
||||
if version:
|
||||
return {"ret": "OK", "version": version}
|
||||
else:
|
||||
return {"ret": "Fetch version failed"}
|
||||
|
||||
|
||||
class DownloadPlayList(BaseModel):
|
||||
dirname: str
|
||||
url: str
|
||||
|
||||
|
||||
# 下载歌单
|
||||
@app.post("/downloadplaylist")
|
||||
async def downloadplaylist(data: DownloadPlayList, Verifcation=Depends(verification)):
|
||||
try:
|
||||
download_proc = await download_playlist(config, data.url, data.dirname)
|
||||
|
||||
async def check_download_proc():
|
||||
# 等待子进程完成
|
||||
exit_code = await download_proc.wait()
|
||||
log.info(f"Download completed with exit code {exit_code}")
|
||||
|
||||
dir_path = os.path.join(config.download_path, data.dirname)
|
||||
log.debug(f"Download dir_path: {dir_path}")
|
||||
# 可能只是部分失败,都需要整理下载目录
|
||||
remove_common_prefix(dir_path)
|
||||
|
||||
asyncio.create_task(check_download_proc())
|
||||
return {"ret": "OK"}
|
||||
except Exception as e:
|
||||
log.exception(f"Execption {e}")
|
||||
|
||||
return {"ret": "Failed download"}
|
||||
|
||||
|
||||
class DownloadOneMusic(BaseModel):
|
||||
name: str = ""
|
||||
url: str
|
||||
|
||||
|
||||
# 下载单首歌曲
|
||||
@app.post("/downloadonemusic")
|
||||
async def downloadonemusic(data: DownloadOneMusic, Verifcation=Depends(verification)):
|
||||
try:
|
||||
await download_one_music(config, data.url, data.name)
|
||||
return {"ret": "OK"}
|
||||
except Exception as e:
|
||||
log.exception(f"Execption {e}")
|
||||
|
||||
return {"ret": "Failed download"}
|
||||
|
||||
|
||||
# 上传 yt-dlp cookies
|
||||
@app.post("/uploadytdlpcookie")
|
||||
async def upload_yt_dlp_cookie(file: UploadFile = File(...)):
|
||||
with open(config.yt_dlp_cookies_path, "wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
return {
|
||||
"ret": "OK",
|
||||
"filename": file.filename,
|
||||
"file_location": config.yt_dlp_cookies_path,
|
||||
}
|
||||
|
||||
|
||||
class PlayListObj(BaseModel):
|
||||
name: str = "" # 歌单名
|
||||
|
||||
|
||||
# 新增歌单
|
||||
@app.post("/playlistadd")
|
||||
async def playlistadd(data: PlayListObj, Verifcation=Depends(verification)):
|
||||
ret = xiaomusic.play_list_add(data.name)
|
||||
if ret:
|
||||
return {"ret": "OK"}
|
||||
return {"ret": "Add failed, may be already exist."}
|
||||
|
||||
|
||||
# 移除歌单
|
||||
@app.post("/playlistdel")
|
||||
async def playlistdel(data: PlayListObj, Verifcation=Depends(verification)):
|
||||
ret = xiaomusic.play_list_del(data.name)
|
||||
if ret:
|
||||
return {"ret": "OK"}
|
||||
return {"ret": "Del failed, may be not exist."}
|
||||
|
||||
|
||||
class PlayListMusicObj(BaseModel):
|
||||
name: str = "" # 歌单名
|
||||
music_list: list[str] # 歌曲名列表
|
||||
|
||||
|
||||
# 歌单新增歌曲
|
||||
@app.post("/playlistaddmusic")
|
||||
async def playlistaddmusic(data: PlayListMusicObj, Verifcation=Depends(verification)):
|
||||
ret = xiaomusic.play_list_add_music(data.name, data.music_list)
|
||||
if ret:
|
||||
return {"ret": "OK"}
|
||||
return {"ret": "Add failed, may be playlist not exist."}
|
||||
|
||||
|
||||
# 歌单移除歌曲
|
||||
@app.post("/playlistdelmusic")
|
||||
async def playlistdelmusic(data: PlayListMusicObj, Verifcation=Depends(verification)):
|
||||
ret = xiaomusic.play_list_del_music(data.name, data.music_list)
|
||||
if ret:
|
||||
return {"ret": "OK"}
|
||||
return {"ret": "Del failed, may be playlist not exist."}
|
||||
|
||||
|
||||
async def file_iterator(file_path, start, end):
|
||||
async with aiofiles.open(file_path, mode="rb") as file:
|
||||
await file.seek(start)
|
||||
chunk_size = 1024
|
||||
while start <= end:
|
||||
read_size = min(chunk_size, end - start + 1)
|
||||
data = await file.read(read_size)
|
||||
if not data:
|
||||
break
|
||||
start += len(data)
|
||||
yield data
|
||||
|
||||
|
||||
def access_key_verification(file_path, key, code):
|
||||
if config.disable_httpauth:
|
||||
return True
|
||||
|
||||
log.debug(f"访问限制接收端[{file_path}, {key}, {code}]")
|
||||
if key is not None:
|
||||
current_key_bytes = key.encode("utf8")
|
||||
correct_key_bytes = (
|
||||
config.httpauth_username + config.httpauth_password
|
||||
).encode("utf8")
|
||||
is_correct_key = secrets.compare_digest(correct_key_bytes, current_key_bytes)
|
||||
if is_correct_key:
|
||||
return True
|
||||
|
||||
if code is not None:
|
||||
current_code_bytes = code.encode("utf8")
|
||||
correct_code_bytes = (
|
||||
hashlib.sha256(
|
||||
(
|
||||
file_path + config.httpauth_username + config.httpauth_password
|
||||
).encode("utf-8")
|
||||
)
|
||||
.hexdigest()
|
||||
.encode("utf-8")
|
||||
)
|
||||
is_correct_code = secrets.compare_digest(correct_code_bytes, current_code_bytes)
|
||||
if is_correct_code:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
range_pattern = re.compile(r"bytes=(\d+)-(\d*)")
|
||||
|
||||
|
||||
def safe_redirect(url):
|
||||
url = try_add_access_control_param(config, url)
|
||||
url = url.replace("\\", "")
|
||||
if not urllib.parse.urlparse(url).netloc and not urllib.parse.urlparse(url).scheme:
|
||||
log.debug(f"redirect to {url}")
|
||||
return RedirectResponse(url=url)
|
||||
return None
|
||||
|
||||
|
||||
@app.get("/music/{file_path:path}")
|
||||
async def music_file(request: Request, file_path: str, key: str = "", code: str = ""):
|
||||
if not access_key_verification(f"/music/{file_path}", key, code):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
absolute_path = os.path.abspath(config.music_path)
|
||||
absolute_file_path = os.path.normpath(os.path.join(absolute_path, file_path))
|
||||
if not absolute_file_path.startswith(absolute_path):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
if not os.path.exists(absolute_file_path):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
# 移除MP3 ID3 v2标签和填充
|
||||
if config.remove_id3tag and is_mp3(file_path):
|
||||
log.info(f"remove_id3tag:{config.remove_id3tag}, is_mp3:True ")
|
||||
temp_mp3_file = remove_id3_tags(absolute_file_path, config)
|
||||
if temp_mp3_file:
|
||||
log.info(f"ID3 tag removed {absolute_file_path} to {temp_mp3_file}")
|
||||
redirect = safe_redirect(f"/music/{temp_mp3_file}")
|
||||
if redirect:
|
||||
return redirect
|
||||
else:
|
||||
log.info(f"No ID3 tag remove needed: {absolute_file_path}")
|
||||
|
||||
if config.convert_to_mp3 and not is_mp3(file_path):
|
||||
temp_mp3_file = convert_file_to_mp3(absolute_file_path, config)
|
||||
if temp_mp3_file:
|
||||
log.info(f"Converted file: {absolute_file_path} to {temp_mp3_file}")
|
||||
redirect = safe_redirect(f"/music/{temp_mp3_file}")
|
||||
if redirect:
|
||||
return redirect
|
||||
else:
|
||||
log.warning(f"Failed to convert file to MP3 format: {absolute_file_path}")
|
||||
|
||||
return FileResponse(absolute_file_path)
|
||||
|
||||
|
||||
@app.options("/music/{file_path:path}")
|
||||
async def music_options():
|
||||
headers = {
|
||||
"Accept-Ranges": "bytes",
|
||||
}
|
||||
return Response(headers=headers)
|
||||
|
||||
|
||||
@app.get("/picture/{file_path:path}")
|
||||
async def get_picture(request: Request, file_path: str, key: str = "", code: str = ""):
|
||||
if not access_key_verification(f"/picture/{file_path}", key, code):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
absolute_path = os.path.abspath(config.picture_cache_path)
|
||||
absolute_file_path = os.path.normpath(os.path.join(absolute_path, file_path))
|
||||
if not absolute_file_path.startswith(absolute_path):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
if not os.path.exists(absolute_file_path):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
return FileResponse(absolute_file_path)
|
||||
|
||||
|
||||
@app.get("/docs", include_in_schema=False)
|
||||
async def get_swagger_documentation(Verifcation=Depends(verification)):
|
||||
return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")
|
||||
|
||||
|
||||
@app.get("/redoc", include_in_schema=False)
|
||||
async def get_redoc_documentation(Verifcation=Depends(verification)):
|
||||
return get_redoc_html(openapi_url="/openapi.json", title="docs")
|
||||
|
||||
|
||||
@app.get("/openapi.json", include_in_schema=False)
|
||||
async def openapi(Verifcation=Depends(verification)):
|
||||
return get_openapi(title=app.title, version=app.version, routes=app.routes)
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
$(function(){
|
||||
$container=$("#cmds");
|
||||
|
||||
const PLAY_TYPE_ONE = 0; // 单曲循环
|
||||
const PLAY_TYPE_ALL = 1; // 全部循环
|
||||
const PLAY_TYPE_RND = 2; // 随机播放
|
||||
append_op_button("play_type_all", "全部循环", "全部循环");
|
||||
append_op_button("play_type_one", "单曲循环", "单曲循环");
|
||||
append_op_button("play_type_rnd", "随机播放", "随机播放");
|
||||
|
||||
append_op_button_name("刷新列表");
|
||||
append_op_button_name("下一首");
|
||||
append_op_button_name("关机");
|
||||
|
||||
$container.append($("<hr>"));
|
||||
|
||||
append_op_button_name("10分钟后关机");
|
||||
append_op_button_name("30分钟后关机");
|
||||
append_op_button_name("60分钟后关机");
|
||||
|
||||
// 拉取现有配置
|
||||
$.get("/getsetting", function(data, status) {
|
||||
console.log(data, status);
|
||||
localStorage.setItem('mi_did', data.mi_did);
|
||||
|
||||
var did = localStorage.getItem('cur_did');
|
||||
var dids = [];
|
||||
if (data.mi_did != null) {
|
||||
dids = data.mi_did.split(',');
|
||||
}
|
||||
console.log('cur_did', did);
|
||||
console.log('dids', dids);
|
||||
if ((dids.length > 0) && (did == null || did == "" || !dids.includes(did))) {
|
||||
did = dids[0];
|
||||
localStorage.setItem('cur_did', did);
|
||||
}
|
||||
|
||||
window.did = did;
|
||||
$.get(`/getvolume?did=${did}`, function(data, status) {
|
||||
console.log(data, status, data["volume"]);
|
||||
$("#volume").val(data.volume);
|
||||
});
|
||||
refresh_music_list();
|
||||
|
||||
$("#did").empty();
|
||||
var dids = data.mi_did.split(',');
|
||||
$.each(dids, function(index, value) {
|
||||
var cur_device = Object.values(data.devices).find(device => device.did === value);
|
||||
if (cur_device) {
|
||||
var option = $('<option></option>')
|
||||
.val(value)
|
||||
.text(cur_device.name)
|
||||
.prop('selected', value === did);
|
||||
$("#did").append(option);
|
||||
|
||||
if (cur_device.play_type == PLAY_TYPE_ALL) {
|
||||
$("#play_type_all").css('background-color', '#b1a8f3');
|
||||
$("#play_type_all").text('✔️ 全部循环');
|
||||
} else if (cur_device.play_type == PLAY_TYPE_ONE) {
|
||||
$("#play_type_one").css('background-color', '#b1a8f3');
|
||||
$("#play_type_one").text('✔️ 单曲循环');
|
||||
} else if (cur_device.play_type == PLAY_TYPE_RND) {
|
||||
$("#play_type_rnd").css('background-color', '#b1a8f3');
|
||||
$("#play_type_rnd").text('✔️ 随机播放');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('cur_did', did);
|
||||
$('#did').change(function() {
|
||||
did = $(this).val();
|
||||
localStorage.setItem('cur_did', did);
|
||||
window.did = did;
|
||||
console.log('cur_did', did);
|
||||
})
|
||||
});
|
||||
|
||||
// 拉取版本
|
||||
$.get("/getversion", function(data, status) {
|
||||
console.log(data, status, data["version"]);
|
||||
$("#version").text(`${data.version}`);
|
||||
});
|
||||
|
||||
// 拉取播放列表
|
||||
function refresh_music_list() {
|
||||
$('#music_list').empty();
|
||||
$.get("/musiclist", function(data, status) {
|
||||
console.log(data, status);
|
||||
$.each(data, function(key, value) {
|
||||
$('#music_list').append($('<option></option>').val(key).text(key));
|
||||
});
|
||||
|
||||
$('#music_list').change(function() {
|
||||
const selectedValue = $(this).val();
|
||||
$('#music_name').empty();
|
||||
const sorted_musics = data[selectedValue].sort(custom_sort_key);
|
||||
$.each(sorted_musics, function(index, item) {
|
||||
$('#music_name').append($('<option></option>').val(item).text(item));
|
||||
});
|
||||
});
|
||||
|
||||
$('#music_list').trigger('change');
|
||||
|
||||
// 获取当前播放列表
|
||||
$.get(`curplaylist?did=${did}`, function(data, status) {
|
||||
if (data != "") {
|
||||
$('#music_list').val(data);
|
||||
$('#music_list').trigger('change');
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 每3秒获取下正在播放的音乐
|
||||
get_playing_music();
|
||||
setInterval(() => {
|
||||
get_playing_music();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
$("#play_music_list").on("click", () => {
|
||||
var music_list = $("#music_list").val();
|
||||
var music_name = $("#music_name").val();
|
||||
let cmd = "播放列表" + music_list + "|" + music_name;
|
||||
sendcmd(cmd);
|
||||
});
|
||||
|
||||
$("#del_music").on("click", () => {
|
||||
var del_music_name = $("#music_name").val();
|
||||
if (confirm(`确定删除歌曲 ${del_music_name} 吗?`)) {
|
||||
console.log(`删除歌曲 ${del_music_name}`);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/delmusic',
|
||||
data: JSON.stringify({"name": del_music_name}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: () => {
|
||||
alert(`删除 ${del_music_name} 成功`);
|
||||
refresh_music_list();
|
||||
},
|
||||
error: () => {
|
||||
alert(`删除 ${del_music_name} 失败`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#playurl").on("click", () => {
|
||||
var url = $("#music-url").val();
|
||||
$.get(`/playurl?url=${url}&did=${did}`, function(data, status) {
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
|
||||
function append_op_button_name(name) {
|
||||
append_op_button(null, name, name);
|
||||
}
|
||||
|
||||
function append_op_button(id, name, cmd) {
|
||||
// 创建按钮
|
||||
const $button = $("<button>");
|
||||
$button.text(name);
|
||||
$button.attr("type", "button");
|
||||
if (id !== null) {
|
||||
$button.attr("id", id);
|
||||
}
|
||||
|
||||
// 设置按钮点击事件
|
||||
$button.on("click", () => {
|
||||
sendcmd(cmd);
|
||||
});
|
||||
|
||||
// 添加按钮到容器
|
||||
$container.append($button);
|
||||
}
|
||||
|
||||
$("#play").on("click", () => {
|
||||
var search_key = $("#music-name").val();
|
||||
var filename = $("#music-filename").val();
|
||||
let cmd = "播放歌曲" + search_key + "|" + filename;
|
||||
sendcmd(cmd);
|
||||
});
|
||||
|
||||
$("#volume").on('change', function () {
|
||||
var value = $(this).val();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/setvolume",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({did: did, volume: value}),
|
||||
success: () => {
|
||||
},
|
||||
error: () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function sendcmd(cmd) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/cmd",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({did: did, cmd: cmd}),
|
||||
success: () => {
|
||||
if (cmd == "刷新列表") {
|
||||
setTimeout(refresh_music_list, 3000);
|
||||
}
|
||||
if (["全部循环", "单曲循环", "随机播放"].includes(cmd)) {
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
// 请求失败时执行的操作
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 监听输入框的输入事件
|
||||
$("#music-name").on('input', function() {
|
||||
var inputValue = $(this).val();
|
||||
// 发送Ajax请求
|
||||
$.ajax({
|
||||
url: "searchmusic", // 服务器端处理脚本
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
data: {
|
||||
name: inputValue
|
||||
},
|
||||
success: function(data) {
|
||||
// 清空datalist
|
||||
$("#autocomplete-list").empty();
|
||||
// 添加新的option元素
|
||||
$.each(data, function(i, item) {
|
||||
$('<option>').val(item).appendTo("#autocomplete-list");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function get_playing_music() {
|
||||
$.get(`/playingmusic?did=${did}`, function(data, status) {
|
||||
console.log(data);
|
||||
if (data.ret == "OK") {
|
||||
if (data.is_playing) {
|
||||
$("#playering-music").text(`【播放中】 ${data.cur_music}`);
|
||||
} else {
|
||||
$("#playering-music").text(`【空闲中】 ${data.cur_music}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function custom_sort_key(a, b) {
|
||||
// 使用正则表达式提取数字前缀
|
||||
const numericPrefixA = a.match(/^(\d+)/) ? parseInt(a.match(/^(\d+)/)[1], 10) : null;
|
||||
const numericPrefixB = b.match(/^(\d+)/) ? parseInt(b.match(/^(\d+)/)[1], 10) : null;
|
||||
|
||||
// 如果两个键都有数字前缀,则按数字大小排序
|
||||
if (numericPrefixA !== null && numericPrefixB !== null) {
|
||||
return numericPrefixA - numericPrefixB;
|
||||
}
|
||||
|
||||
// 如果一个键有数字前缀而另一个没有,则有数字前缀的键排在前面
|
||||
if (numericPrefixA !== null) return -1;
|
||||
if (numericPrefixB !== null) return 1;
|
||||
|
||||
// 如果两个键都没有数字前缀,则按照常规字符串排序
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
|
||||
});
|
||||
516
xiaomusic/static/default/app.js
Normal file
@@ -0,0 +1,516 @@
|
||||
$(function(){
|
||||
$container=$("#cmds");
|
||||
|
||||
append_op_button_name("加入收藏");
|
||||
append_op_button_name("取消收藏");
|
||||
|
||||
append_op_button_name("上一首");
|
||||
append_op_button_name("关机");
|
||||
append_op_button_name("下一首");
|
||||
|
||||
const PLAY_TYPE_ONE = 0; // 单曲循环
|
||||
const PLAY_TYPE_ALL = 1; // 全部循环
|
||||
const PLAY_TYPE_RND = 2; // 随机播放
|
||||
const PLAY_TYPE_SIN = 3; // 单曲播放
|
||||
const PLAY_TYPE_SEQ = 4; // 顺序播放
|
||||
append_op_button("play_type_all", "全部循环", "全部循环");
|
||||
append_op_button("play_type_one", "单曲循环", "单曲循环");
|
||||
append_op_button("play_type_rnd", "随机播放", "随机播放");
|
||||
append_op_button("play_type_sin", "单曲播放", "单曲播放");
|
||||
append_op_button("play_type_seq", "顺序播放", "顺序播放");
|
||||
|
||||
append_op_button_name("刷新列表");
|
||||
|
||||
$container.append($("<hr>"));
|
||||
|
||||
append_op_button_name("10分钟后关机");
|
||||
append_op_button_name("30分钟后关机");
|
||||
append_op_button_name("60分钟后关机");
|
||||
|
||||
var offset = 0;
|
||||
var duration = 0;
|
||||
let no_warning = localStorage.getItem('no-warning');
|
||||
// 拉取现有配置
|
||||
$.get("/getsetting", function(data, status) {
|
||||
console.log(data, status);
|
||||
localStorage.setItem('mi_did', data.mi_did);
|
||||
|
||||
var did = localStorage.getItem('cur_did');
|
||||
var dids = [];
|
||||
if (data.mi_did != null) {
|
||||
dids = data.mi_did.split(',');
|
||||
}
|
||||
console.log('cur_did', did);
|
||||
console.log('dids', dids);
|
||||
if ((dids.length > 0) && (did == null || did == "" || !dids.includes(did))) {
|
||||
did = dids[0];
|
||||
localStorage.setItem('cur_did', did);
|
||||
}
|
||||
|
||||
window.did = did;
|
||||
$.get(`/getvolume?did=${did}`, function(data, status) {
|
||||
console.log(data, status, data["volume"]);
|
||||
$("#volume").val(data.volume);
|
||||
});
|
||||
refresh_music_list();
|
||||
|
||||
$("#did").empty();
|
||||
var dids = data.mi_did.split(',');
|
||||
$.each(dids, function(index, value) {
|
||||
var cur_device = Object.values(data.devices).find(device => device.did === value);
|
||||
if (cur_device) {
|
||||
var option = $('<option></option>')
|
||||
.val(value)
|
||||
.text(cur_device.name)
|
||||
.prop('selected', value === did);
|
||||
$("#did").append(option);
|
||||
|
||||
if (value === did) {
|
||||
if (cur_device.play_type == PLAY_TYPE_ALL) {
|
||||
$("#play_type_all").css('background-color', '#b1a8f3');
|
||||
$("#play_type_all").text('✔️ 全部循环');
|
||||
} else if (cur_device.play_type == PLAY_TYPE_ONE) {
|
||||
$("#play_type_one").css('background-color', '#b1a8f3');
|
||||
$("#play_type_one").text('✔️ 单曲循环');
|
||||
} else if (cur_device.play_type == PLAY_TYPE_RND) {
|
||||
$("#play_type_rnd").css('background-color', '#b1a8f3');
|
||||
$("#play_type_rnd").text('✔️ 随机播放');
|
||||
} else if (cur_device.play_type == PLAY_TYPE_SIN) {
|
||||
$("#play_type_sin").css('background-color', '#b1a8f3');
|
||||
$("#play_type_sin").text('✔️ 单曲播放');
|
||||
} else if (cur_device.play_type == PLAY_TYPE_SEQ) {
|
||||
$("#play_type_seq").css('background-color', '#b1a8f3');
|
||||
$("#play_type_seq").text('✔️ 顺序播放');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('cur_did', did);
|
||||
$('#did').change(function() {
|
||||
did = $(this).val();
|
||||
localStorage.setItem('cur_did', did);
|
||||
window.did = did;
|
||||
console.log('cur_did', did);
|
||||
location.reload();
|
||||
})
|
||||
});
|
||||
|
||||
function compareVersion(version1, version2) {
|
||||
const v1 = version1.split('.').map(Number);
|
||||
const v2 = version2.split('.').map(Number);
|
||||
const len = Math.max(v1.length, v2.length);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = v1[i] || 0;
|
||||
const num2 = v2[i] || 0;
|
||||
if (num1 > num2) return 1;
|
||||
if (num1 < num2) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 拉取版本
|
||||
$.get("/getversion", function(data, status) {
|
||||
console.log(data, status, data["version"]);
|
||||
$("#version").text(`${data.version}`);
|
||||
|
||||
$.get("/latestversion", function(ret, status) {
|
||||
console.log(ret, status);
|
||||
if (ret.ret == "OK") {
|
||||
const result = compareVersion(ret.version, data.version);
|
||||
if (result > 0) {
|
||||
console.log(`${ret.version} is greater than ${data.version}`);
|
||||
$("#versionnew").text("🆕");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function _refresh_music_list(callback) {
|
||||
$('#music_list').empty();
|
||||
$.get("/musiclist", function(data, status) {
|
||||
console.log(data, status);
|
||||
$.each(data, function(key, value) {
|
||||
let cnt = value.length;
|
||||
$('#music_list').append($('<option></option>').val(key).text(`${key} (${cnt})`));
|
||||
});
|
||||
|
||||
$('#music_list').change(function() {
|
||||
const selectedValue = $(this).val();
|
||||
localStorage.setItem('cur_playlist', selectedValue);
|
||||
$('#music_name').empty();
|
||||
$.each(data[selectedValue], function(index, item) {
|
||||
$('#music_name').append($('<option></option>').val(item).text(item));
|
||||
});
|
||||
});
|
||||
|
||||
$('#music_list').trigger('change');
|
||||
|
||||
// 获取当前播放列表
|
||||
$.get(`/curplaylist?did=${did}`, function(playlist, status) {
|
||||
if (playlist != "") {
|
||||
$('#music_list').val(playlist);
|
||||
$('#music_list').trigger('change');
|
||||
} else {
|
||||
// 使用本地记录的
|
||||
playlist = localStorage.getItem('cur_playlist');
|
||||
if (data.includes(playlist)) {
|
||||
$('#music_list').val(playlist);
|
||||
$('#music_list').trigger('change');
|
||||
}
|
||||
}
|
||||
})
|
||||
callback();
|
||||
})
|
||||
}
|
||||
|
||||
// 拉取播放列表
|
||||
function refresh_music_list() {
|
||||
// 刷新列表时清空并临时禁用搜索框
|
||||
const searchInput = document.getElementById('search');
|
||||
const oriPlaceHolder = searchInput.placeholder
|
||||
const oriValue = searchInput.value
|
||||
const inputEvent = new Event('input', { bubbles: true });
|
||||
searchInput.value = '';
|
||||
// 分发事件,让其他控件改变状态
|
||||
searchInput.dispatchEvent(inputEvent);
|
||||
searchInput.disabled = true;
|
||||
searchInput.placeholder = '请等待...';
|
||||
|
||||
_refresh_music_list(() => {
|
||||
// 刷新完成再启用
|
||||
searchInput.disabled = false;
|
||||
searchInput.value = oriValue
|
||||
searchInput.dispatchEvent(inputEvent);
|
||||
searchInput.placeholder = oriPlaceHolder;
|
||||
// 每3秒获取下正在播放的音乐
|
||||
get_playing_music();
|
||||
setInterval(() => {
|
||||
get_playing_music();
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function do_play_music_list(listname, musicname) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/playmusiclist",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({did: did, listname: listname, musicname: musicname}),
|
||||
success: () => {
|
||||
console.log("do_play_music_list succ", listname, musicname);
|
||||
},
|
||||
error: () => {
|
||||
console.log("do_play_music_list failed", listname, musicname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#play_music_list").on("click", () => {
|
||||
var music_list = $("#music_list").val();
|
||||
var music_name = $("#music_name").val();
|
||||
if (no_warning) {
|
||||
do_play_music_list(music_list, music_name);
|
||||
return;
|
||||
}
|
||||
$.get(`/musicinfo?name=${music_name}`, function(data, status) {
|
||||
console.log(data);
|
||||
if (data.ret == "OK") {
|
||||
validHost(data.url) && do_play_music_list(music_list, music_name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#web_play").on("click", () => {
|
||||
const music_name = $("#music_name").val();
|
||||
$.get(`/musicinfo?name=${music_name}`, function(data, status) {
|
||||
console.log(data);
|
||||
if (data.ret == "OK") {
|
||||
validHost(data.url) && $('audio').attr('src',data.url);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#del_music").on("click", () => {
|
||||
var del_music_name = $("#music_name").val();
|
||||
if (confirm(`确定删除歌曲 ${del_music_name} 吗?`)) {
|
||||
console.log(`删除歌曲 ${del_music_name}`);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/delmusic',
|
||||
data: JSON.stringify({"name": del_music_name}),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: () => {
|
||||
alert(`删除 ${del_music_name} 成功`);
|
||||
refresh_music_list();
|
||||
},
|
||||
error: () => {
|
||||
alert(`删除 ${del_music_name} 失败`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#playurl").on("click", () => {
|
||||
var url = $("#music-url").val();
|
||||
const encoded_url = encodeURIComponent(url);
|
||||
$.get(`/playurl?url=${encoded_url}&did=${did}`, function(data, status) {
|
||||
console.log(data);
|
||||
});
|
||||
});
|
||||
|
||||
function append_op_button_name(name) {
|
||||
append_op_button(null, name, name);
|
||||
}
|
||||
|
||||
function append_op_button(id, name, cmd) {
|
||||
// 创建按钮
|
||||
const $button = $("<button>");
|
||||
$button.text(name);
|
||||
$button.attr("type", "button");
|
||||
if (id !== null) {
|
||||
$button.attr("id", id);
|
||||
}
|
||||
|
||||
// 设置按钮点击事件
|
||||
$button.on("click", () => {
|
||||
sendcmd(cmd);
|
||||
});
|
||||
|
||||
// 添加按钮到容器
|
||||
$container.append($button);
|
||||
}
|
||||
|
||||
function do_play_music(musicname, searchkey) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/playmusic",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({did: did, musicname: musicname, searchkey: searchkey}),
|
||||
success: () => {
|
||||
console.log("do_play_music succ", musicname, searchkey);
|
||||
},
|
||||
error: () => {
|
||||
console.log("do_play_music failed", musicname, searchkey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$("#play").on("click", () => {
|
||||
var search_key = $("#music-name").val();
|
||||
if (search_key == null) {
|
||||
search_key = "";
|
||||
}
|
||||
var filename = $("#music-filename").val();
|
||||
if (filename == null || filename == "") {
|
||||
filename = search_key;
|
||||
}
|
||||
do_play_music(filename, search_key);
|
||||
});
|
||||
|
||||
$("#volume").on('change', function () {
|
||||
var value = $(this).val();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/setvolume",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({did: did, volume: value}),
|
||||
success: () => {
|
||||
},
|
||||
error: () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function check_status_refresh_music_list(retries) {
|
||||
$.get("/cmdstatus", function(data) {
|
||||
if (data.status === "finish") {
|
||||
refresh_music_list();
|
||||
} else if (retries > 0) {
|
||||
setTimeout(function() {
|
||||
check_status_refresh_music_list(retries - 1);
|
||||
}, 1000); // 等待1秒后重试
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendcmd(cmd) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/cmd",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
data: JSON.stringify({did: did, cmd: cmd}),
|
||||
success: () => {
|
||||
if (cmd == "刷新列表") {
|
||||
check_status_refresh_music_list(3); // 最多重试3次
|
||||
}
|
||||
if (["全部循环", "单曲循环", "随机播放", "单曲播放", "顺序播放"].includes(cmd)) {
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
// 请求失败时执行的操作
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 监听输入框的输入事件
|
||||
function debounce(func, delay) {
|
||||
let timeout;
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById('search');
|
||||
const musicSelect = document.getElementById('music-name');
|
||||
const musicSelectLabel = document.getElementById('music-name-label');
|
||||
|
||||
searchInput.addEventListener('input', debounce(function() {
|
||||
const query = searchInput.value.trim();
|
||||
|
||||
if (query.length === 0) {
|
||||
musicSelect.innerHTML = '';
|
||||
musicSelect.style.display = 'none'
|
||||
musicSelectLabel.style.display = 'none'
|
||||
return;
|
||||
}
|
||||
|
||||
musicSelect.style.display = 'block'
|
||||
musicSelectLabel.style.display = 'block'
|
||||
fetch(`/searchmusic?name=${encodeURIComponent(query)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
musicSelect.innerHTML = ''; // 清空现有选项
|
||||
|
||||
// 找到的优先显示
|
||||
if (data.length > 0) {
|
||||
data.forEach(song => {
|
||||
const option = document.createElement('option');
|
||||
option.value = song
|
||||
option.textContent = song
|
||||
musicSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加用户输入作为一个选项
|
||||
const userOption = document.createElement('option');
|
||||
userOption.value = query;
|
||||
userOption.textContent = `使用关键词播放: ${query}`;
|
||||
musicSelect.appendChild(userOption);
|
||||
|
||||
// 提示没找到
|
||||
if (data.length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = '没有匹配的结果';
|
||||
option.disabled = true;
|
||||
musicSelect.appendChild(option);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error);
|
||||
});
|
||||
}, 500));
|
||||
|
||||
// 动态显示保存文件名输入框
|
||||
const musicNameSelect = document.getElementById('music-name');
|
||||
const musicFilenameInput = document.getElementById('music-filename');
|
||||
function updateInputVisibility() {
|
||||
const selectedOption = musicNameSelect.options[musicNameSelect.selectedIndex];
|
||||
var startsWithKeyword;
|
||||
if (musicNameSelect.options.length === 0) {
|
||||
startsWithKeyword = false;
|
||||
} else {
|
||||
startsWithKeyword = selectedOption.text.startsWith('使用关键词联网搜索:');
|
||||
}
|
||||
|
||||
if (startsWithKeyword) {
|
||||
musicFilenameInput.style.display = 'block';
|
||||
musicFilenameInput.placeholder = '请输入保存为的文件名称(默认:' + selectedOption.value + ')';
|
||||
} else {
|
||||
musicFilenameInput.style.display = 'none';
|
||||
}
|
||||
}
|
||||
// 观察元素修改
|
||||
const observer = new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'childList') {
|
||||
updateInputVisibility()
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(musicNameSelect, { childList: true });
|
||||
// 监听用户输入
|
||||
musicNameSelect.addEventListener('change', updateInputVisibility);
|
||||
|
||||
function get_playing_music() {
|
||||
$.get(`/playingmusic?did=${did}`, function(data, status) {
|
||||
console.log(data);
|
||||
if (data.ret == "OK") {
|
||||
if (data.is_playing) {
|
||||
$("#playering-music").text(`【播放中】 ${data.cur_music}`);
|
||||
} else {
|
||||
$("#playering-music").text(`【空闲中】 ${data.cur_music}`);
|
||||
}
|
||||
offset = data.offset;
|
||||
duration = data.duration;
|
||||
}
|
||||
});
|
||||
}
|
||||
setInterval(()=>{
|
||||
if (duration > 0) {
|
||||
offset++;
|
||||
$("#progress").val(offset / duration * 100);
|
||||
$("#play-time").text(`${formatTime(offset)}/${formatTime(duration)}`)
|
||||
}else{
|
||||
$("#play-time").text(`${formatTime(0)}/${formatTime(0)}`)
|
||||
}
|
||||
},1000)
|
||||
function formatTime(seconds) {
|
||||
var minutes = Math.floor(seconds / 60);
|
||||
var remainingSeconds =Math.floor(seconds % 60);
|
||||
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
$("audio").on("error", (e) => {
|
||||
console.log('网页播放出现错误: ', 'color: #007acc;', e.currentTarget.error.code,e.currentTarget.error.message);
|
||||
alert(e.currentTarget.error.code==4 ? "无法打开媒体文件,XIAOMUSIC_HOSTNAME或端口地址错误,请重新设置" : "在线播放失败,请截图反馈: "+e.currentTarget.error.message);
|
||||
});
|
||||
function validHost(url) {
|
||||
//如果 localStorage 中有 no-warning 则直接返回true
|
||||
if (no_warning) {
|
||||
return true;
|
||||
}
|
||||
const local = location.host;
|
||||
const host = new URL(url).host;
|
||||
// 如果当前页面的Host与设置中的XIAOMUSIC_HOSTNAME、PORT一致, 不再提醒
|
||||
if (local === host) {
|
||||
|
||||
localStorage.setItem('no-warning', 'true');
|
||||
// 设置全局变量
|
||||
no_warning = true;
|
||||
return true;
|
||||
}
|
||||
// 如果当前页面的Host与设置中的XIAOMUSIC_HOSTNAME、PORT不一致
|
||||
const validHost = document.getElementById('valid-host');
|
||||
let validFlag = false;
|
||||
$('#local-host').text(local);
|
||||
$('#setting-host').text(host);
|
||||
validHost.showModal();
|
||||
//监听validHost的close事件
|
||||
function _handleClose() {
|
||||
console.log('%c提醒HOST不一致弹窗,用户已选择: ', 'color: #007acc;', validHost.returnValue);
|
||||
if (validHost.returnValue == "no-warning") {
|
||||
localStorage.setItem('no-warning', 'true');
|
||||
no_warning = true;
|
||||
validFlag = true;
|
||||
}
|
||||
validHost.removeEventListener('close', _handleClose)
|
||||
}
|
||||
validHost.addEventListener('close', _handleClose)
|
||||
return validFlag;
|
||||
}
|
||||
});
|
||||
@@ -2,12 +2,22 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Debug For XiaoMusic</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1722380134">
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1733199308">
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script src="/static/jquery-3.7.1.min.js?version=1722380134"></script>
|
||||
<script src="./jquery-3.7.1.min.js?version=1733199308"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Z09NC1K7ZW');
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var vConsole = new window.VConsole();
|
||||
113
xiaomusic/static/default/downloadtool.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>歌曲下载工具</title>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1733199308">
|
||||
<script src="./jquery-3.7.1.min.js?version=1733199308"></script>
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Z09NC1K7ZW');
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>歌曲下载工具</h1>
|
||||
|
||||
<div class="rows">
|
||||
<!-- 歌单的输入 -->
|
||||
<label for="playlistUrl">输入歌单 URL:</label>
|
||||
<input type="text" id="playlistUrl" value="https://m.bilibili.com/video/BV1WUsDezE88">
|
||||
|
||||
<label for="dirname">输入歌单名字:</label>
|
||||
<input type="text" id="dirname" placeholder="流行歌曲">
|
||||
|
||||
<button id="downloadPlaylistBtn">下载歌单</button>
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="rows">
|
||||
|
||||
<!-- 单曲的输入 -->
|
||||
<label for="songUrl">输入歌曲 URL:</label>
|
||||
<input type="text" id="songUrl" value="https://m.bilibili.com/video/BV1qD4y1U7fs">
|
||||
|
||||
<label for="songName">输入歌曲名字:</label>
|
||||
<input type="text" id="songName" placeholder="歌曲名">
|
||||
|
||||
<button id="downloadSongBtn">下载单曲</button>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// 下载歌单
|
||||
$('#downloadPlaylistBtn').click(function() {
|
||||
var playlistUrl = $('#playlistUrl').val();
|
||||
var dirname = $('#dirname').val();
|
||||
|
||||
if (!playlistUrl || !dirname) {
|
||||
alert('请填写完整的歌单 URL 和歌单名字');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
dirname: dirname,
|
||||
url: playlistUrl
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/downloadplaylist",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: (msg) => {
|
||||
alert('歌单下载请求已发送!');
|
||||
console.log(response);
|
||||
},
|
||||
error: (msg) => {
|
||||
alert('歌单下载请求失败,请重试。');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 下载单曲
|
||||
$('#downloadSongBtn').click(function() {
|
||||
var songName = $('#songName').val();
|
||||
var songUrl = $('#songUrl').val();
|
||||
|
||||
if (!songUrl || !songName) {
|
||||
alert('请填写完整的歌曲 URL 和歌曲名字');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
name: songName,
|
||||
url: songUrl
|
||||
};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/downloadonemusic",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: (msg) => {
|
||||
alert('单曲下载请求已发送!');
|
||||
console.log(response);
|
||||
},
|
||||
error: (msg) => {
|
||||
alert('单曲下载请求失败,请重试。');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
110
xiaomusic/static/default/index.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="./jquery-3.7.1.min.js?version=1733199308"></script>
|
||||
<script src="./app.js?version=1733199308"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1733199308">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Z09NC1K7ZW');
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script>
|
||||
var vConsole = new window.VConsole();
|
||||
</script>
|
||||
-->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h2>小爱音箱操控面板
|
||||
(<a id="version" href="https://github.com/hanxi/xiaomusic/blob/main/CHANGELOG.md">版本未知</a>)
|
||||
<span id="versionnew" class="blink"></span>
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
<div class="rows">
|
||||
<select id="did">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="cmds">
|
||||
<a class="button" href="./setting.html">设置</a>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div style="margin: 20px;">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<label for="search">搜索歌曲:</label>
|
||||
<input type="text" id="search" placeholder="请输入搜索关键词(如:MV高清版 周杰伦 七里香)">
|
||||
|
||||
<label for="music-name" id="music-name-label" style="display: none;">确认选择:</label>
|
||||
<select id="music-name" style="display: none;">
|
||||
<!-- 动态生成选项 -->
|
||||
</select>
|
||||
|
||||
<input id="music-filename" type="text" placeholder="请输入保存为的文件名称(如:周杰伦七里香)" style="display: none;"></input>
|
||||
<div style="display: flex; align-items: center">
|
||||
<progress id="progress" value="0" max="100" style="width: 270px"></progress>
|
||||
<div id="play-time" style="margin-left: 10px">00:00/00:00</div>
|
||||
</div>
|
||||
<div>
|
||||
<button id="play">播放</button>
|
||||
<div id="playering-music" class="text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<label for="music_list">播放列表:</label>
|
||||
<select id="music_list"></select>
|
||||
<label for="music_name">歌曲:</label>
|
||||
<select id="music_name"></select>
|
||||
<div>
|
||||
<button id="play_music_list">播放选中歌曲</button>
|
||||
<button id="del_music">删除选中歌曲</button>
|
||||
<button id="web_play">网页播放</button>
|
||||
</div>
|
||||
<div class="play_pannel">
|
||||
<audio autoplay controls src=""></audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<input id="music-url" type="text" value="https://lhttp.qtfm.cn/live/4915/64k.mp3"></input>
|
||||
<button id="playurl">播放链接</button>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||
</footer>
|
||||
<dialog id="valid-host">
|
||||
<form method="dialog">
|
||||
<p>当前页面的HOST与设置中的HOST不一致,请检查是否设置错误</p>
|
||||
<p>当前HOST: <span id="local-host"></span></p>
|
||||
<p>设置中的HOST: <span id="setting-host"></span></p>
|
||||
<div class="btn-list">
|
||||
<a href="./setting.html" target="_blank">立即修改</a>
|
||||
<button value="no-warning" type="submit">继续并不再显示</button>
|
||||
<button value="cancle" type="submit">取消</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,9 +2,20 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>M3U to JSON Converter</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1722380134">
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1733199308">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Z09NC1K7ZW');
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script>
|
||||
BIN
xiaomusic/static/default/qrcode.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
253
xiaomusic/static/default/setting.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>小爱音箱操控面板</title>
|
||||
<script src="./jquery-3.7.1.min.js?version=1733199308"></script>
|
||||
<script src="./setting.js?version=1733199308"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./style.css?version=1733199308">
|
||||
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments)};
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-Z09NC1K7ZW');
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||
<script>
|
||||
var vConsole = new window.VConsole();
|
||||
</script>
|
||||
-->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h2>小爱音箱设置面板
|
||||
(<a id="version" href="https://github.com/hanxi/xiaomusic/blob/main/CHANGELOG.md">
|
||||
版本未知
|
||||
</a>)
|
||||
</h2>
|
||||
<hr>
|
||||
|
||||
<div class="rows">
|
||||
<label for="mi_did">*勾选设备(至少勾选1个):</label>
|
||||
<div id="mi_did">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div id="setting">
|
||||
<div class="rows">
|
||||
<label for="account">*小米账号:</label>
|
||||
<input id="account" type="text" placeholder="填写小米登录账号" />
|
||||
|
||||
<label for="password">*小米密码:</label>
|
||||
<input id="password" type="password" placeholder="填写小米登录密码" />
|
||||
|
||||
<label for="hostname">*XIAOMUSIC_HOSTNAME(IP或域名):</label>
|
||||
<input id="hostname" type="text" />
|
||||
</div>
|
||||
<hr>
|
||||
<div class="rows">
|
||||
<label for="verbose">是否开启调试日志:</label>
|
||||
<select id="verbose">
|
||||
<option value="true" selected>true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
|
||||
<label for="port">监听端口(修改后需要重启):</label>
|
||||
<input id="port" type="number" value="8090" />
|
||||
|
||||
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
|
||||
<input id="public_port" type="number" value="0" />
|
||||
|
||||
<label for="group_list">设备分组配置:<a href="https://github.com/hanxi/xiaomusic/issues/65#issuecomment-2215736529" target="_blank">文档</a></label>
|
||||
<input id="group_list" type="text" placeholder="did1:组名1,did2:组名1,did3:组名2" />
|
||||
|
||||
<label for="music_path">音乐目录:</label>
|
||||
<input id="music_path" type="text" value="music" />
|
||||
|
||||
<label for="download_path">音乐下载目录(必须是music的子目录):</label>
|
||||
<input id="download_path" type="text" value='music/download' />
|
||||
|
||||
<label for="conf_path">配置文件目录:</label>
|
||||
<input id="conf_path" type="text" />
|
||||
|
||||
<label for="cache_dir">缓存文件目录:</label>
|
||||
<input id="cache_dir" type="text" />
|
||||
|
||||
<label for="temp_path">临时文件目录:</label>
|
||||
<input id="temp_path" type="text" value="music/tmp" />
|
||||
|
||||
<label for="ffmpeg_location">ffmpeg路径:</label>
|
||||
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin" />
|
||||
|
||||
<label for="log_file">日志路径:</label>
|
||||
<input id="log_file" type="text" value="xiaomusic.log.txt" />
|
||||
|
||||
<label for="active_cmd">允许唤醒的命令:</label>
|
||||
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop" />
|
||||
|
||||
<label for="exclude_dirs">忽略目录(逗号分割):</label>
|
||||
<input id="exclude_dirs" type="text" value="@eaDir,tmp" />
|
||||
|
||||
<label for="music_path_depth">目录深度:</label>
|
||||
<input id="music_path_depth" type="number" value="10" />
|
||||
|
||||
<label for="search_prefix">XIAOMUSIC_SEARCH(歌曲下载方式):</label>
|
||||
<select id="search_prefix">
|
||||
<option value="bilisearch:">bilisearch:</option>
|
||||
<option value="ytsearch:">ytsearch:</option>
|
||||
</select>
|
||||
|
||||
<label for="proxy">XIAOMUSIC_PROXY(ytsearch需要):</label>
|
||||
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080" />
|
||||
|
||||
<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="convert_to_mp3">转换为MP3:</label>
|
||||
<select id="convert_to_mp3">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="miio_tts_command">MiIO tts 指令(解决部分型号没有提示音的问题):</label>
|
||||
<input id="miio_tts_command" type="text" placeholder="如:5 或者 5-3" />
|
||||
|
||||
<label for="disable_httpauth">关闭控制台密码验证:</label>
|
||||
<select id="disable_httpauth">
|
||||
<option value="true" selected>true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
<label for="httpauth_username">控制台账户:</label>
|
||||
<input id="httpauth_username" type="text" value="" />
|
||||
<label for="httpauth_password">控制台密码:</label>
|
||||
<input id="httpauth_password" type="password" value="" />
|
||||
|
||||
<label for="disable_download">关闭下载功能:</label>
|
||||
<select id="disable_download">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="use_music_audio_id">触屏版显示歌曲ID:</label>
|
||||
<input id="use_music_audio_id" type="text" value="1582971365183456177" />
|
||||
<label for="use_music_id">触屏版显示歌曲分段ID:</label>
|
||||
<input id="use_music_id" type="text" value="355454500" />
|
||||
|
||||
<label for="fuzzy_match_cutoff">模糊匹配阈值(0.1~0.9):</label>
|
||||
<input id="fuzzy_match_cutoff" type="number" value="0.6" />
|
||||
|
||||
<label for="enable_fuzzy_match">开启模糊搜索:</label>
|
||||
<select id="enable_fuzzy_match">
|
||||
<option value="true" selected>true</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
|
||||
<label for="use_music_api">型号兼容模式:</label>
|
||||
<select id="use_music_api">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="continue_play">启用继续播放(可能导致兼容性问题):</label>
|
||||
<select id="continue_play">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="pull_ask_sec">获取对话记录间隔(秒):</label>
|
||||
<input id="pull_ask_sec" type="number" value="1" />
|
||||
|
||||
<label for="delay_sec">下一首歌延迟播放秒数:</label>
|
||||
<input id="delay_sec" type="number" value="3" />
|
||||
|
||||
<label for="stop_tts_msg">停止提示音:</label>
|
||||
<input id="stop_tts_msg" type="text" value="收到,再见" />
|
||||
<label for="play_type_one_tts_msg">单曲循环提示音:</label>
|
||||
<input id="play_type_one_tts_msg" type="text" value="已经设置为单曲循环" />
|
||||
<label for="play_type_all_tts_msg">全部循环提示音:</label>
|
||||
<input id="play_type_all_tts_msg" type="text" value="已经设置为全部循环" />
|
||||
<label for="play_type_rnd_tts_msg">随机播放提示音:</label>
|
||||
<input id="play_type_rnd_tts_msg" type="text" value="已经设置为随机播放" />
|
||||
<label for="play_type_sin_tts_msg">单曲播放提示音:</label>
|
||||
<input id="play_type_sin_tts_msg" type="text" value="已经设置为单曲播放" />
|
||||
<label for="play_type_seq_tts_msg">顺序播放提示音:</label>
|
||||
<input id="play_type_seq_tts_msg" type="text" value="已经设置为顺序播放" />
|
||||
|
||||
<label for="keywords_playlocal">播放本地歌曲口令:</label>
|
||||
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲" />
|
||||
<label for="keywords_play">播放歌曲口令:</label>
|
||||
<input id="keywords_play" type="text" value="播放歌曲,放歌曲" />
|
||||
<label for="keywords_playlist">播放列表口令:</label>
|
||||
<input id="keywords_playlist" type="text" value="播放列表,播放歌单" />
|
||||
<label for="keywords_stop">停止口令:</label>
|
||||
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放" />
|
||||
|
||||
<label for="enable_yt_dlp_cookies">启用yt-dlp-cookies(需要先上传yt-dlp-cookies.txt文件):</label>
|
||||
<select id="enable_yt_dlp_cookies">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="get_ask_by_mina">特殊型号获取对话记录:</label>
|
||||
<select id="get_ask_by_mina">
|
||||
<option value="true">true</option>
|
||||
<option value="false" selected>false</option>
|
||||
</select>
|
||||
|
||||
<label for="music_list_url">歌单地址:</label>
|
||||
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json" />
|
||||
|
||||
<label for="music_list_json">歌单内容:<a href="https://github.com/hanxi/xiaomusic/issues/78" target="_blank">格式文档</a></label>
|
||||
<textarea id="music_list_json" type="text"></textarea>
|
||||
|
||||
<label for="crontab_json">定时任务:<a href="https://github.com/hanxi/xiaomusic/issues/182" target="_blank">格式文档</a></label>
|
||||
<textarea id="crontab_json" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<div class="rows">
|
||||
<label for="yt_dlp_cookies_file">上传yt_dlp_cookies.txt文件:<a href="https://github.com/hanxi/xiaomusic/issues/210" target="_blank">文档</a></label>
|
||||
<input id="yt_dlp_cookies_file" name="file" type="file">
|
||||
<button id="upload_yt_dlp_cookie">上传</button>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<button onclick="location.href='/static/default/index.html';">返回首页</button>
|
||||
<button id="get_music_list">获取歌单</button>
|
||||
<button class="save-button">保存</button>
|
||||
<hr>
|
||||
|
||||
<button id="refresh_music_tag">刷新tag</button>
|
||||
<button id="clear_cache">清空缓存</button>
|
||||
<a class="button" href="/downloadlog" download="xiaomusic.txt">下载日志文件</a>
|
||||
|
||||
<hr>
|
||||
|
||||
<button onclick="location.href='/docs';">查看接口文档</button>
|
||||
<a class="button" href="./m3u.html" target="_blank">m3u文件转换</a>
|
||||
<a class="button" href="./downloadtool.html" target="_blank">歌曲下载工具</a>
|
||||
<hr>
|
||||
|
||||
<a class="button" href="./debug.html" target="_blank">调试工具</a>
|
||||
<a class="button" href="https://afdian.com/a/imhanxi" target="_blank">💰 爱发电</a>
|
||||
<a class="button" href="https://github.com/hanxi/xiaomusic" target="_blank">点个 Star ⭐</a>
|
||||
|
||||
<div class="rows">
|
||||
<img class="qrcode" src="./qrcode.png" alt="请涵曦喝奶茶🧋">
|
||||
</div>
|
||||
<footer>
|
||||
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,13 +16,19 @@ $(function(){
|
||||
});
|
||||
};
|
||||
|
||||
function updateCheckbox(selector, mi_did, device_list) {
|
||||
function updateCheckbox(selector, mi_did, device_list,accountPassValid) {
|
||||
// 清除现有的内容
|
||||
$(selector).empty();
|
||||
|
||||
// 将 mi_did 字符串通过逗号分割转换为数组,以便于判断默认选中项
|
||||
var selected_dids = mi_did.split(',');
|
||||
|
||||
//如果device_list为空,则可能是未设置小米账号密码或者已设置密码,但是没有过小米验证,此处需要提示用户
|
||||
if (device_list.length == 0) {
|
||||
const loginTips = accountPassValid ? `<div class="login-tips">未发现可用的小爱设备,请检查账号密码是否输错,并关闭加速代理或在<a href="https://www.mi.com">小米官网</a>登陆过人脸或滑块验证。如仍未解决。请根据<a href="https://github.com/hanxi/xiaomusic/issues/99">FAQ</a>的内容解决问题。</div>` : `<div class="login-tips">未发现可用的小爱设备,请先在下面的输入框中设置小米的<b>账号、密码</b></div>`;
|
||||
$(selector).append(loginTips);
|
||||
return;
|
||||
}
|
||||
$.each(device_list, function(index, device) {
|
||||
var did = device.miotDID;
|
||||
var hardware = device.hardware;
|
||||
@@ -64,7 +70,8 @@ $(function(){
|
||||
// 拉取现有配置
|
||||
$.get("/getsetting?need_device_list=true", function(data, status) {
|
||||
console.log(data, status);
|
||||
updateCheckbox("#mi_did", data.mi_did, data.device_list);
|
||||
const accountPassValid = data.account && data.password;
|
||||
updateCheckbox("#mi_did", data.mi_did, data.device_list, accountPassValid);
|
||||
|
||||
// 初始化显示
|
||||
for (const key in data) {
|
||||
@@ -137,4 +144,51 @@ $(function(){
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#refresh_music_tag").on("click", () => {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/refreshmusictag",
|
||||
contentType: "application/json",
|
||||
success: (res) => {
|
||||
console.log(res);
|
||||
alert(res.ret);
|
||||
},
|
||||
error: (res) => {
|
||||
console.log(res);
|
||||
alert(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#upload_yt_dlp_cookie").on("click", () => {
|
||||
var fileInput = document.getElementById('yt_dlp_cookies_file');
|
||||
var file = fileInput.files[0]; // 获取文件对象
|
||||
if (file) {
|
||||
var formData = new FormData();
|
||||
formData.append("file", file);
|
||||
$.ajax({
|
||||
url: "/uploadytdlpcookie",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(res) {
|
||||
console.log(res);
|
||||
alert("上传成功");
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
console.log(res);
|
||||
alert("上传失败");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert("请选择一个文件");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$("#clear_cache").on("click", () => {
|
||||
localStorage.clear();
|
||||
});
|
||||
});
|
||||
165
xiaomusic/static/default/style.css
Normal file
@@ -0,0 +1,165 @@
|
||||
.button {
|
||||
line-height: 50px;
|
||||
font-size: 14px;
|
||||
}
|
||||
button, .button {
|
||||
margin: 10px;
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
background-color: #008CBA;
|
||||
}
|
||||
button:active, .button:active {
|
||||
font-weight:bold;
|
||||
background-color: #007CBA;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
input,select {
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
textarea{
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.custom-checkbox {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle; /* 确保与标签垂直居中对齐 */
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: inline-block;
|
||||
width: 260px;
|
||||
background-color: #fff;
|
||||
border: 0px solid #ccc;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
vertical-align: middle; /* 确保与复选框垂直居中对齐 */
|
||||
margin-left: 1px; /* 给复选框和标签之间一些距离,如果需要的话 */
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 10px;
|
||||
width: 150px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.blink {
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.login-tips {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.login-tips a {
|
||||
color: rgb(9, 105, 218);
|
||||
text-decoration: underline;
|
||||
}
|
||||
#valid-host {
|
||||
padding: 20px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#valid-host::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
||||
#valid-host form input {
|
||||
width: fit-content;
|
||||
margin: 0;
|
||||
height: fit-content;
|
||||
}
|
||||
#valid-host p {
|
||||
word-break: break-all;
|
||||
}
|
||||
#valid-host p span {
|
||||
color: red;
|
||||
}
|
||||
#valid-host a, #valid-host a:visited {
|
||||
color: rgb(9, 105, 218);;
|
||||
text-decoration: underline;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#valid-host a:hover {
|
||||
color: rgb(9, 95, 198);
|
||||
}
|
||||
#valid-host .btn-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#valid-host .btn-list button {
|
||||
width: fit-content;
|
||||
min-width: 60px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
background-color: #008CBA;
|
||||
}
|
||||
#valid-host .btn-list button:hover {
|
||||
font-weight:bold;
|
||||
background-color: #007CBA;
|
||||
}
|
||||
BIN
xiaomusic/static/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
xiaomusic/static/icons/android/android-launchericon-144-144.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
xiaomusic/static/icons/android/android-launchericon-192-192.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
xiaomusic/static/icons/android/android-launchericon-48-48.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
xiaomusic/static/icons/android/android-launchericon-512-512.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
xiaomusic/static/icons/android/android-launchericon-72-72.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
xiaomusic/static/icons/android/android-launchericon-96-96.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
xiaomusic/static/icons/ios/100.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
xiaomusic/static/icons/ios/1024.png
Normal file
|
After Width: | Height: | Size: 990 KiB |
BIN
xiaomusic/static/icons/ios/114.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
xiaomusic/static/icons/ios/120.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
xiaomusic/static/icons/ios/128.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
xiaomusic/static/icons/ios/144.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
xiaomusic/static/icons/ios/152.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
xiaomusic/static/icons/ios/16.png
Normal file
|
After Width: | Height: | Size: 657 B |
BIN
xiaomusic/static/icons/ios/167.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
xiaomusic/static/icons/ios/180.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
xiaomusic/static/icons/ios/192.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
xiaomusic/static/icons/ios/20.png
Normal file
|
After Width: | Height: | Size: 891 B |
BIN
xiaomusic/static/icons/ios/256.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
xiaomusic/static/icons/ios/29.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
xiaomusic/static/icons/ios/32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
xiaomusic/static/icons/ios/40.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
xiaomusic/static/icons/ios/50.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
xiaomusic/static/icons/ios/512.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
xiaomusic/static/icons/ios/57.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
xiaomusic/static/icons/ios/58.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
xiaomusic/static/icons/ios/60.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
xiaomusic/static/icons/ios/64.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
xiaomusic/static/icons/ios/72.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
xiaomusic/static/icons/ios/76.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
xiaomusic/static/icons/ios/80.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
xiaomusic/static/icons/ios/87.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
xiaomusic/static/icons/windows11/LargeTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
xiaomusic/static/icons/windows11/LargeTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
xiaomusic/static/icons/windows11/LargeTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
xiaomusic/static/icons/windows11/LargeTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
xiaomusic/static/icons/windows11/LargeTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
xiaomusic/static/icons/windows11/SmallTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
xiaomusic/static/icons/windows11/SmallTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
xiaomusic/static/icons/windows11/SmallTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
xiaomusic/static/icons/windows11/SmallTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
xiaomusic/static/icons/windows11/SmallTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
xiaomusic/static/icons/windows11/SplashScreen.scale-100.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
xiaomusic/static/icons/windows11/SplashScreen.scale-125.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
xiaomusic/static/icons/windows11/SplashScreen.scale-150.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
xiaomusic/static/icons/windows11/SplashScreen.scale-200.png
Normal file
|
After Width: | Height: | Size: 394 KiB |
BIN
xiaomusic/static/icons/windows11/SplashScreen.scale-400.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
xiaomusic/static/icons/windows11/Square150x150Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
xiaomusic/static/icons/windows11/Square150x150Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
xiaomusic/static/icons/windows11/Square150x150Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
xiaomusic/static/icons/windows11/Square150x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
xiaomusic/static/icons/windows11/Square150x150Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 379 KiB |
|
After Width: | Height: | Size: 760 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |