├── .github
└── workflows
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ └── publish.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── demo.gif
├── docs
├── Makefile
└── source
│ ├── conf.py
│ ├── guide.md
│ ├── index.md
│ └── scrcpy.md
├── poetry.lock
├── pyproject.toml
├── scrcpy
├── __init__.py
├── const.py
├── control.py
├── core.py
└── scrcpy-server.jar
├── scrcpy_ui
├── __init__.py
├── main.py
├── main.ui
└── ui_main.py
├── scripts
├── check.py
└── lint.py
└── tests
├── test_control.py
├── test_core.py
├── test_video_data.pkl
└── utils.py
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | tests:
13 | name: "Test on python ${{ matrix.python-version }}, ${{ matrix.os }}"
14 | runs-on: "${{ matrix.os }}"
15 | strategy:
16 | matrix:
17 | # Should match the python versions in pyproject.toml and publish.yml
18 | python-version: ["3.9", "3.10", "3.11", "3.12"]
19 | os: [windows-latest, ubuntu-latest, macos-latest]
20 | env:
21 | OS: ${{ matrix.os }}
22 | PYTHON: ${{ matrix.python-version }}
23 | steps:
24 | - uses: actions/checkout@v1
25 | - name: Set up Python ${{ matrix.python-version }}
26 | uses: actions/setup-python@v5
27 | with:
28 | python-version: ${{ matrix.python-version }}
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip poetry
32 | poetry config virtualenvs.create false --local
33 | poetry install
34 | - name: Static type and format checking
35 | run: python scripts/check.py
36 | - name: Test and generate coverage report
37 | run: |
38 | pytest --cov=scrcpy --cov-report=xml
39 | - name: Upload coverage to Codecov
40 | uses: codecov/codecov-action@v2
41 | with:
42 | files: ./coverage.xml
43 | directory: ./coverage/reports/
44 | flags: unittests
45 | env_vars: OS,PYTHON
46 | fail_ci_if_error: false
47 | verbose: true
48 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '17 6 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37 | # Learn more:
38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39 |
40 | steps:
41 | - name: Checkout repository
42 | uses: actions/checkout@v2
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | jobs:
9 | tests:
10 | name: "Publish on python ${{ matrix.python-version }}, ${{ matrix.os }}"
11 | runs-on: "${{ matrix.os }}"
12 | strategy:
13 | matrix:
14 | # Should match the python versions in pyproject.toml and ci.yml
15 | python-version: ["3.9", "3.10", "3.11", "3.12"]
16 | os: [windows-latest, ubuntu-latest, macos-latest]
17 | env:
18 | OS: ${{ matrix.os }}
19 | PYTHON: ${{ matrix.python-version }}
20 | steps:
21 | - uses: actions/checkout@v1
22 | - name: Set up Python ${{ matrix.python-version }}
23 | uses: actions/setup-python@v5
24 | with:
25 | python-version: ${{ matrix.python-version }}
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip poetry
29 | poetry config virtualenvs.create false --local
30 | poetry install
31 | - name: Static type and format checking
32 | run: python scripts/check.py
33 | - name: Test and generate coverage report
34 | run: |
35 | pytest --cov=scrcpy --cov-report=xml
36 | - name: Upload coverage to Codecov
37 | uses: codecov/codecov-action@v2
38 | with:
39 | files: ./coverage.xml
40 | directory: ./coverage/reports/
41 | flags: unittests
42 | env_vars: OS,PYTHON
43 | fail_ci_if_error: false
44 | verbose: true
45 | publish:
46 | name: Publish package
47 | needs: tests
48 | if: startsWith(github.ref, 'refs/tags/')
49 | runs-on: ubuntu-latest
50 | steps:
51 | - uses: actions/checkout@v1
52 | - name: Set up Python
53 | uses: actions/setup-python@v5
54 | with:
55 | # Use the latest python version we support
56 | python-version: "3.12"
57 | - name: Install dependencies
58 | run: |
59 | python -m pip install --upgrade pip poetry
60 | poetry config virtualenvs.create false --local
61 | poetry install
62 | - name: Build
63 | run: poetry build
64 | - name: Publish
65 | env:
66 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
67 | run: poetry publish
68 | docs:
69 | name: Generate document
70 | runs-on: ubuntu-latest
71 | steps:
72 | - uses: actions/checkout@v1
73 | - name: Set up Python
74 | uses: actions/setup-python@v5
75 | with:
76 | # Just use the latest python version we support
77 | python-version: "3.12"
78 | - name: Install dependencies
79 | run: |
80 | python -m pip install --upgrade pip poetry
81 | poetry config virtualenvs.create false --local
82 | poetry install
83 | - name: Build docs
84 | run: cd docs && make html
85 | - name: Deploy
86 | uses: peaceiris/actions-gh-pages@v3
87 | with:
88 | github_token: ${{ secrets.GITHUB_TOKEN }}
89 | publish_dir: docs/_build/html
90 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python scripts from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 | .pytest_cache
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
59 |
60 | .idea/
61 | adb
62 |
63 | pyside-setup/
64 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021-2021 Lengyue and others
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python Scrcpy Client
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | This package allows you to view and control android device in realtime.
22 |
23 | 
24 |
25 | Note: This gif is compressed and experience lower quality than actual.
26 |
27 | ## How to use
28 | To begin with, you need to install this package via pip:
29 | ```shell
30 | pip install scrcpy-client[ui]
31 | ```
32 | Then, you can start `py-scrcpy` to view the demo:
33 |
34 | Note: you can ignore `[ui]` if you don't want to view the demo ui
35 |
36 | ## Document
37 | Here is the document GitHub page: [Documentation](https://leng-yue.github.io/py-scrcpy-client/)
38 | Also, you can check `scrcpy_ui/main.py` for a full functional demo.
39 |
40 | ## Contribution & Development
41 | Already implemented all functions in scrcpy server 1.20.
42 | Please check scrcpy server 1.20 source code: [Link](https://github.com/Genymobile/scrcpy/tree/v1.20/server)
43 |
44 | ## Reference & Appreciation
45 | - Core: [scrcpy](https://github.com/Genymobile/scrcpy)
46 | - Idea: [py-android-viewer](https://github.com/razumeiko/py-android-viewer)
47 | - CI: [index.py](https://github.com/index-py/index.py)
48 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leng-yue/py-scrcpy-client/ad57b15934c71cb24555bbb3f5050b1d6c908de5/demo.gif
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | import os
14 | import sys
15 | sys.path.insert(0, '../')
16 | sys.path.insert(0, '../../')
17 | sys.path.insert(0, '../../../')
18 |
19 |
20 | # -- Project information -----------------------------------------------------
21 |
22 | project = 'Py Scrcpy'
23 | copyright = '2021, Lengyue'
24 | author = 'Lengyue'
25 |
26 |
27 | # -- General configuration ---------------------------------------------------
28 |
29 | # Add any Sphinx extension module names here, as strings. They can be
30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
31 | # ones.
32 | extensions = [
33 | 'sphinx.ext.autodoc',
34 | 'sphinx.ext.viewcode',
35 | 'sphinx.ext.todo',
36 | 'sphinx.ext.napoleon',
37 | 'myst_parser'
38 | ]
39 |
40 | # Add any paths that contain templates here, relative to this directory.
41 | templates_path = ['_templates']
42 |
43 | # The language for content autogenerated by Sphinx. Refer to documentation
44 | # for a list of supported languages.
45 | #
46 | # This is also used if you do content translation via gettext catalogs.
47 | # Usually you set "language" from the command line for these cases.
48 | language = 'en'
49 |
50 | # List of patterns, relative to source directory, that match files and
51 | # directories to ignore when looking for source files.
52 | # This pattern also affects html_static_path and html_extra_path.
53 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
54 |
55 |
56 | # -- Options for HTML output -------------------------------------------------
57 |
58 | # The theme to use for HTML and HTML Help pages. See the documentation for
59 | # a list of builtin themes.
60 | #
61 | html_theme = 'alabaster'
62 | html_theme_options = {
63 | "show_powered_by": False,
64 | "github_user": "leng-yue",
65 | "github_repo": "py-scrcpy-client",
66 | "github_banner": True,
67 | "show_related": False,
68 | "note_bg": "#FFF59C",
69 | }
70 |
71 | # Add any paths that contain custom static files (such as style sheets) here,
72 | # relative to this directory. They are copied after the builtin static files,
73 | # so a file named "default.css" will overwrite the builtin "default.css".
74 | html_static_path = ['_static']
75 |
76 |
77 | # -- Extension configuration -------------------------------------------------
78 |
79 | # -- Options for todo extension ----------------------------------------------
80 |
81 | # If true, `todo` and `todoList` produce output, else they produce nothing.
82 | todo_include_todos = True
--------------------------------------------------------------------------------
/docs/source/guide.md:
--------------------------------------------------------------------------------
1 | # Guide
2 |
3 | In this article, you will learn to use this project in 10 minutes.
4 |
5 | ## ADB connection
6 | Since this project depends on [adbutils](https://github.com/openatx/adbutils),
7 | user don't have to download adb manually on windows and macos.
8 | However, **linux users still need to install adb manually**.
9 | - Debian user can use `apt install adb` to install ADB.
10 |
11 | ```python
12 | import scrcpy
13 | # If you already know the device serial
14 | client = scrcpy.Client(device="DEVICE SERIAL")
15 | # You can also pass an ADBClient instance to it
16 | from adbutils import adb
17 | adb.connect("127.0.0.1:5555")
18 | client = scrcpy.Client(device=adb.device_list()[0])
19 | ```
20 |
21 | For more information, you should go to [adbutils's webpage](https://github.com/openatx/adbutils).
22 |
23 | ## Bind events
24 | This project's core follow event sender and receiver structure.
25 | This means you can add multiple listener to the same stream.
26 |
27 | ```python
28 | import cv2
29 |
30 | def on_frame(frame):
31 | # If you set non-blocking (default) in constructor, the frame event receiver
32 | # may receive None to avoid blocking event.
33 | if frame is not None:
34 | # frame is an bgr numpy ndarray (cv2' default format)
35 | cv2.imshow("viz", frame)
36 | cv2.waitKey(10)
37 |
38 | client.add_listener(scrcpy.EVENT_FRAME, on_frame)
39 | ```
40 |
41 | [Optional] You can also add a listener to listen the `init` event.
42 | ```python
43 | def on_init():
44 | # Print device name
45 | print(client.device_name)
46 | client.add_listener(scrcpy.EVENT_INIT, on_init)
47 | ```
48 |
49 | Then, you can start the client
50 | ```python
51 | client.start()
52 | ```
53 | [Optional] you can start the client with `threaded=True`, then the frame loop will be executed in a new thread,
54 | and the main thread won't be blocked.
55 | ```python
56 | client.start(threaded=True)
57 | ```
58 |
59 | ## Send actions
60 | You can find all actions in the `API:scrcpy.control` submodule.
61 |
62 | The core will create a control instance to itself automatically.
63 | For example, you can send a touch event by
64 | ```python
65 | # Mousedown
66 | client.control.touch(100, 200, scrcpy.ACTION_DOWN)
67 | # Mouseup
68 | client.control.touch(100, 200, scrcpy.ACTION_UP)
69 | ```
70 |
71 | ## Get device information
72 | ```python
73 | # Resolution
74 | client.resolution
75 | # Screenshot / Last frame
76 | client.last_frame
77 | # Device Name
78 | client.device_name
79 | ```
80 |
81 | ## Reduce CPU usage
82 | You can use `max_width`, `bitrate`, and `max_fps` parameter to limit the bitrate of the video stream.
83 | After reducing the bitrate of video stream, the H264 decoder can save much CPU resources.
84 | This is very helpful when you don't need a 10 ms level experience. (You probably only need 5 fps in most automation).
85 |
--------------------------------------------------------------------------------
/docs/source/index.md:
--------------------------------------------------------------------------------
1 | # Python Scrcpy Client
2 |
3 | Full functional and high performance scrcpy client.
4 |
5 | ## Contents
6 |
7 | ### Guide
8 | ```{eval-rst}
9 | .. toctree::
10 | :maxdepth: 4
11 |
12 | guide
13 | ```
14 |
15 | ### API
16 | ```{eval-rst}
17 | .. toctree::
18 | :maxdepth: 4
19 |
20 | scrcpy
21 | ```
22 |
23 | ## Indices and tables
24 | ```{eval-rst}
25 | - :ref:`genindex`
26 |
27 | - :ref:`modindex`
28 | - :ref:`search`
29 | ```
--------------------------------------------------------------------------------
/docs/source/scrcpy.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | ## Module description
4 | ```{eval-rst}
5 | .. automodule:: scrcpy
6 | :members:
7 | :undoc-members:
8 | :show-inheritance:
9 | ```
10 |
11 | ## Submodules
12 |
13 | ### scrcpy.core module
14 |
15 | ```{eval-rst}
16 | .. automodule:: scrcpy.core
17 | :members:
18 | :undoc-members:
19 | :show-inheritance:
20 | ```
21 | ### scrcpy.control module
22 |
23 | ```{eval-rst}
24 | .. automodule:: scrcpy.control
25 | :members:
26 | :undoc-members:
27 | :show-inheritance:
28 | ```
29 |
30 | ### scrcpy.const module
31 | ```{eval-rst}
32 | .. automodule:: scrcpy.const
33 | :members:
34 | :undoc-members:
35 | :show-inheritance:
36 | ```
37 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "adbutils"
5 | version = "2.8.0"
6 | description = "Pure Python Adb Library"
7 | optional = false
8 | python-versions = ">=3.8"
9 | files = [
10 | {file = "adbutils-2.8.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:88c9beacbffc52df3740dced9b44230e7f28148d9e7d8bc4e0666f5d503b1cb2"},
11 | {file = "adbutils-2.8.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:6becbb3f4c4cf4bb06c3519588b1c63832e12abd629d2a98a7f7e244f83ea9db"},
12 | {file = "adbutils-2.8.0-py3-none-win32.whl", hash = "sha256:7aaf43015b49d39151461881b752e81af2010b38bc9a70b0948b5e1e1bcc4b73"},
13 | {file = "adbutils-2.8.0-py3-none-win_amd64.whl", hash = "sha256:739d69c8e266c4d65512ff77551ef57b2178e0870bdfda1bef034dd0f2ad9433"},
14 | {file = "adbutils-2.8.0.tar.gz", hash = "sha256:2e6f580b412014783d054febcb95c7ff3962e4120b80b340419790f71f2d4489"},
15 | ]
16 |
17 | [package.dependencies]
18 | apkutils2 = ">=1.0.0,<2.0"
19 | deprecation = ">=2.0.6,<3.0"
20 | Pillow = "*"
21 | requests = "*"
22 | retry = ">=0.9"
23 |
24 | [[package]]
25 | name = "alabaster"
26 | version = "0.7.13"
27 | description = "A configurable sidebar-enabled Sphinx theme"
28 | optional = false
29 | python-versions = ">=3.6"
30 | files = [
31 | {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"},
32 | {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
33 | ]
34 |
35 | [[package]]
36 | name = "apkutils2"
37 | version = "1.0.0"
38 | description = "Utils for parsing apk."
39 | optional = false
40 | python-versions = "*"
41 | files = [
42 | {file = "apkutils2-1.0.0.tar.gz", hash = "sha256:c5ae8f86d3ebee6a59fc014d88507741d7f3f9ab183bab34b44d011fe878660b"},
43 | ]
44 |
45 | [package.dependencies]
46 | cigam = "*"
47 | pyelftools = "*"
48 | xmltodict = "*"
49 |
50 | [[package]]
51 | name = "attrs"
52 | version = "22.2.0"
53 | description = "Classes Without Boilerplate"
54 | optional = false
55 | python-versions = ">=3.6"
56 | files = [
57 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
58 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
59 | ]
60 |
61 | [package.extras]
62 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
63 | dev = ["attrs[docs,tests]"]
64 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
65 | tests = ["attrs[tests-no-zope]", "zope.interface"]
66 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
67 |
68 | [[package]]
69 | name = "av"
70 | version = "12.0.0"
71 | description = "Pythonic bindings for FFmpeg's libraries."
72 | optional = false
73 | python-versions = ">=3.8"
74 | files = [
75 | {file = "av-12.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9d0890553951f76c479a9f2bb952aebae902b1c7d52feea614d37e1cd728a44"},
76 | {file = "av-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5d7f229a253c2e3fea9682c09c5ae179bd6d5d2da38d89eb7f29ef7bed10cb2f"},
77 | {file = "av-12.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b3555d143aacf02e0446f6030319403538eba4dc713c18dfa653a2a23e7f9c"},
78 | {file = "av-12.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607e13b2c2b26159a37525d7b6f647a32ce78711fccff23d146d3e255ffa115f"},
79 | {file = "av-12.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f0b4cfb89f4f06b339c766f92648e798a96747d4163f2fa78660d1ab1f1b5e"},
80 | {file = "av-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:41dcb8c269fa58a56edf3a3c814c32a0c69586827f132b4e395a951b0ce14fad"},
81 | {file = "av-12.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fa78fbe0e4469226512380180063116105048c66cb12e18ab4b518466c57e6c"},
82 | {file = "av-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60a869be1d6af916e65ea461cb93922f5db0698655ed7a7eae7c3ecd4af4debb"},
83 | {file = "av-12.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df61811cc551c186f0a0e530d97b8b139453534d0f92c1790a923f666522ceda"},
84 | {file = "av-12.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99cd2fc53091ebfb9a2fa9dd3580267f5bd1c040d0efd99fbc1a162576b271cb"},
85 | {file = "av-12.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a6d4f1e261df48932128e6495772faa4cc23f5dd1512eec73daab82ad9f3240"},
86 | {file = "av-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:6aec88e41a498b1e01e2dce5371557e20f9a51aae0c16decc5924ec0be2e22b6"},
87 | {file = "av-12.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90eb8f2d548e96cbc6f78e89c911cdb15a3d80fd944f31111660ce45939cd037"},
88 | {file = "av-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7f3a02910e77d750dbd516256a16db15030e5371530ff5a5ae902dc03d9005d"},
89 | {file = "av-12.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2477cc51526aa50575313d66e5e8ad7ab944588469be5e557b360ed572ae536"},
90 | {file = "av-12.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a2f47149d3ca6deb79f3e515b8bef50e27ebdb160813e6d67dba77278d2a7883"},
91 | {file = "av-12.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3306e4a3ce8b5bfcc3075793d4ed3a2df69179d8fba22cb944a6164dc235dfb6"},
92 | {file = "av-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:dc1b742e7f6df1b499fb960bd6697d1dd8e7ada7484a041a8c20e70a87225f53"},
93 | {file = "av-12.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0183be6889e835e1b074b4037bfce4fd44671c606cf1c4ab92ea2f271b544aec"},
94 | {file = "av-12.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:57337f20b208292ec8d3b11e4d289d8688a43d728174850a81b865d3253fff2c"},
95 | {file = "av-12.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ec915e8f6521545a38566eefc281042ee504ea3cee0618d8558e4920588b3b2"},
96 | {file = "av-12.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33ad5c0a23c45b72bd6bd47f3b2c1adcd2935ee3d0b6178ed66bba62b964ff31"},
97 | {file = "av-12.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc3a652b12c93120514d56cf025da47442c5ba51530cdf7ba3660257dbb0de1"},
98 | {file = "av-12.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:037f793dd1ef4a1f57f090191a7f803ad10ec82da0d04ea26bbe0b8a145fe927"},
99 | {file = "av-12.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc532376aa264722fae55063abd1871d17a563dc895978e142c8ecfcdeb3a2e8"},
100 | {file = "av-12.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf0c4bc40a0af8a30f4cd96f3be6f19fbce0f21222d7fcec148e085127153f7"},
101 | {file = "av-12.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81cedd1c072fbebf606724c406b1a1b00adc711f1dfd2bc04c633ce39d8439d8"},
102 | {file = "av-12.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02d60f48be9f15dcda37d50f3ce8d7249d9a455643d4322dd3449986bacfc628"},
103 | {file = "av-12.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d2619e4c26d661eecfc404f7d739d8b35f0dcef353fabe61512e030254b7031"},
104 | {file = "av-12.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:1892cc91c888d101777d5432d54e0554c11d1c3a2c65d02a2cae0a2256a8fbb9"},
105 | {file = "av-12.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4819e3ef6c3a44ef6f75907229133a1ee7f688245b2cf49b6b8e969a81ca72c9"},
106 | {file = "av-12.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb16bb314cf1503b0250fc46b2c455ee196584231101be0123f4f78638227b62"},
107 | {file = "av-12.0.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3e6a62bda9a1e144feeb59bbee046d7a2d98399634a30f57e4990197313c158"},
108 | {file = "av-12.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08175ffbafa3a70c7b2f81083e160e34122a208cdf70f150b8f5d02c2de6965"},
109 | {file = "av-12.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e1d255be317b7c1ebdc4dae98935b9f3869161112dc829c625e54f90d8bdd7ab"},
110 | {file = "av-12.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:17964b36e08435910aabd5b3f7dca12f99536902529767d276026bc08f94ced7"},
111 | {file = "av-12.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d5f78de29edee06ddcdd4c2b759914575492d6a0cd4de2ce31ee63a4953eff"},
112 | {file = "av-12.0.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:309b32bc97158d0f0c19e273b8e17a855a86806b7194aebc23bd497326cff11f"},
113 | {file = "av-12.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c409c71bd9c7c2f8d018c822f36b1447cfa96eca158381a96f3319bb0ff6e79e"},
114 | {file = "av-12.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:08fc5eaef60a257d622998626e233bf3ff90d2f817f6695d6a27e0ffcfe9dcff"},
115 | {file = "av-12.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746ab0eff8a7a21a6c6d16e6b6e61709527eba2ad1a524d92a01bb60d02a3df7"},
116 | {file = "av-12.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:013b3ac3de3aa1c137af0cedafd364fd1c7524ab3e1cd53e04564fd1632ac04d"},
117 | {file = "av-12.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa55923527648f51ac005e44fe2797ebc67f53ad4850e0194d3753761ee33a2"},
118 | {file = "av-12.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:35d514f4dee0cf67e9e6b2a65fb4a28f98da88e71e8c7f7960bd04625d9fe965"},
119 | {file = "av-12.0.0.tar.gz", hash = "sha256:bcf21ebb722d4538b4099e5a78f730d78814dd70003511c185941dba5651b14d"},
120 | ]
121 |
122 | [[package]]
123 | name = "babel"
124 | version = "2.11.0"
125 | description = "Internationalization utilities"
126 | optional = false
127 | python-versions = ">=3.6"
128 | files = [
129 | {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"},
130 | {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"},
131 | ]
132 |
133 | [package.dependencies]
134 | pytz = ">=2015.7"
135 |
136 | [[package]]
137 | name = "black"
138 | version = "22.12.0"
139 | description = "The uncompromising code formatter."
140 | optional = false
141 | python-versions = ">=3.7"
142 | files = [
143 | {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"},
144 | {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"},
145 | {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"},
146 | {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"},
147 | {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"},
148 | {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"},
149 | {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"},
150 | {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"},
151 | {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"},
152 | {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"},
153 | {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"},
154 | {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"},
155 | ]
156 |
157 | [package.dependencies]
158 | click = ">=8.0.0"
159 | mypy-extensions = ">=0.4.3"
160 | pathspec = ">=0.9.0"
161 | platformdirs = ">=2"
162 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
163 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
164 |
165 | [package.extras]
166 | colorama = ["colorama (>=0.4.3)"]
167 | d = ["aiohttp (>=3.7.4)"]
168 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
169 | uvloop = ["uvloop (>=0.15.2)"]
170 |
171 | [[package]]
172 | name = "certifi"
173 | version = "2023.7.22"
174 | description = "Python package for providing Mozilla's CA Bundle."
175 | optional = false
176 | python-versions = ">=3.6"
177 | files = [
178 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
179 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
180 | ]
181 |
182 | [[package]]
183 | name = "charset-normalizer"
184 | version = "3.0.1"
185 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
186 | optional = false
187 | python-versions = "*"
188 | files = [
189 | {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"},
190 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"},
191 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"},
192 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"},
193 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"},
194 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"},
195 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"},
196 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"},
197 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"},
198 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"},
199 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"},
200 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"},
201 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"},
202 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"},
203 | {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"},
204 | {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"},
205 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"},
206 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"},
207 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"},
208 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"},
209 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"},
210 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"},
211 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"},
212 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"},
213 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"},
214 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"},
215 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"},
216 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"},
217 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"},
218 | {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"},
219 | {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"},
220 | {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"},
221 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"},
222 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"},
223 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"},
224 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"},
225 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"},
226 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"},
227 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"},
228 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"},
229 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"},
230 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"},
231 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"},
232 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"},
233 | {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"},
234 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"},
235 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"},
236 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"},
237 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"},
238 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"},
239 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"},
240 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"},
241 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"},
242 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"},
243 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"},
244 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"},
245 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"},
246 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"},
247 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"},
248 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"},
249 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"},
250 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"},
251 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"},
252 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"},
253 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"},
254 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"},
255 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"},
256 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"},
257 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"},
258 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"},
259 | {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"},
260 | {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"},
261 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"},
262 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"},
263 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"},
264 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"},
265 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"},
266 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"},
267 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"},
268 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"},
269 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"},
270 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"},
271 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"},
272 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"},
273 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"},
274 | {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"},
275 | {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"},
276 | {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"},
277 | ]
278 |
279 | [[package]]
280 | name = "cigam"
281 | version = "0.0.3"
282 | description = "magic"
283 | optional = false
284 | python-versions = "*"
285 | files = [
286 | {file = "cigam-0.0.3-py3-none-any.whl", hash = "sha256:8fcf65d7361f0372c53780e861307abd1f11a94b6204fa653ba3f38277822783"},
287 | ]
288 |
289 | [[package]]
290 | name = "click"
291 | version = "8.1.3"
292 | description = "Composable command line interface toolkit"
293 | optional = false
294 | python-versions = ">=3.7"
295 | files = [
296 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
297 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
298 | ]
299 |
300 | [package.dependencies]
301 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
302 |
303 | [[package]]
304 | name = "colorama"
305 | version = "0.4.6"
306 | description = "Cross-platform colored terminal text."
307 | optional = false
308 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
309 | files = [
310 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
311 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
312 | ]
313 |
314 | [[package]]
315 | name = "coverage"
316 | version = "7.1.0"
317 | description = "Code coverage measurement for Python"
318 | optional = false
319 | python-versions = ">=3.7"
320 | files = [
321 | {file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"},
322 | {file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"},
323 | {file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"},
324 | {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"},
325 | {file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"},
326 | {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"},
327 | {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"},
328 | {file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"},
329 | {file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"},
330 | {file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"},
331 | {file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"},
332 | {file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"},
333 | {file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"},
334 | {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"},
335 | {file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"},
336 | {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"},
337 | {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"},
338 | {file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"},
339 | {file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"},
340 | {file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"},
341 | {file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"},
342 | {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"},
343 | {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"},
344 | {file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"},
345 | {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"},
346 | {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"},
347 | {file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"},
348 | {file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"},
349 | {file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"},
350 | {file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"},
351 | {file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"},
352 | {file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"},
353 | {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"},
354 | {file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"},
355 | {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"},
356 | {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"},
357 | {file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"},
358 | {file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"},
359 | {file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"},
360 | {file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"},
361 | {file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"},
362 | {file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"},
363 | {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"},
364 | {file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"},
365 | {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"},
366 | {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"},
367 | {file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"},
368 | {file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"},
369 | {file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"},
370 | {file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"},
371 | {file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"},
372 | ]
373 |
374 | [package.dependencies]
375 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
376 |
377 | [package.extras]
378 | toml = ["tomli"]
379 |
380 | [[package]]
381 | name = "decorator"
382 | version = "5.1.1"
383 | description = "Decorators for Humans"
384 | optional = false
385 | python-versions = ">=3.5"
386 | files = [
387 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
388 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
389 | ]
390 |
391 | [[package]]
392 | name = "deprecation"
393 | version = "2.1.0"
394 | description = "A library to handle automated deprecations"
395 | optional = false
396 | python-versions = "*"
397 | files = [
398 | {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
399 | {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
400 | ]
401 |
402 | [package.dependencies]
403 | packaging = "*"
404 |
405 | [[package]]
406 | name = "docutils"
407 | version = "0.17.1"
408 | description = "Docutils -- Python Documentation Utilities"
409 | optional = false
410 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
411 | files = [
412 | {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
413 | {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
414 | ]
415 |
416 | [[package]]
417 | name = "exceptiongroup"
418 | version = "1.1.0"
419 | description = "Backport of PEP 654 (exception groups)"
420 | optional = false
421 | python-versions = ">=3.7"
422 | files = [
423 | {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
424 | {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
425 | ]
426 |
427 | [package.extras]
428 | test = ["pytest (>=6)"]
429 |
430 | [[package]]
431 | name = "flake8"
432 | version = "7.0.0"
433 | description = "the modular source code checker: pep8 pyflakes and co"
434 | optional = false
435 | python-versions = ">=3.8.1"
436 | files = [
437 | {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
438 | {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
439 | ]
440 |
441 | [package.dependencies]
442 | mccabe = ">=0.7.0,<0.8.0"
443 | pycodestyle = ">=2.11.0,<2.12.0"
444 | pyflakes = ">=3.2.0,<3.3.0"
445 |
446 | [[package]]
447 | name = "idna"
448 | version = "3.4"
449 | description = "Internationalized Domain Names in Applications (IDNA)"
450 | optional = false
451 | python-versions = ">=3.5"
452 | files = [
453 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
454 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
455 | ]
456 |
457 | [[package]]
458 | name = "imagesize"
459 | version = "1.4.1"
460 | description = "Getting image size from png/jpeg/jpeg2000/gif file"
461 | optional = false
462 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
463 | files = [
464 | {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
465 | {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
466 | ]
467 |
468 | [[package]]
469 | name = "iniconfig"
470 | version = "2.0.0"
471 | description = "brain-dead simple config-ini parsing"
472 | optional = false
473 | python-versions = ">=3.7"
474 | files = [
475 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
476 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
477 | ]
478 |
479 | [[package]]
480 | name = "isort"
481 | version = "5.11.5"
482 | description = "A Python utility / library to sort Python imports."
483 | optional = false
484 | python-versions = ">=3.7.0"
485 | files = [
486 | {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"},
487 | {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"},
488 | ]
489 |
490 | [package.extras]
491 | colors = ["colorama (>=0.4.3,<0.5.0)"]
492 | pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
493 | plugins = ["setuptools"]
494 | requirements-deprecated-finder = ["pip-api", "pipreqs"]
495 |
496 | [[package]]
497 | name = "jinja2"
498 | version = "3.1.2"
499 | description = "A very fast and expressive template engine."
500 | optional = false
501 | python-versions = ">=3.7"
502 | files = [
503 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
504 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
505 | ]
506 |
507 | [package.dependencies]
508 | MarkupSafe = ">=2.0"
509 |
510 | [package.extras]
511 | i18n = ["Babel (>=2.7)"]
512 |
513 | [[package]]
514 | name = "markdown-it-py"
515 | version = "2.1.0"
516 | description = "Python port of markdown-it. Markdown parsing, done right!"
517 | optional = false
518 | python-versions = ">=3.7"
519 | files = [
520 | {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"},
521 | {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"},
522 | ]
523 |
524 | [package.dependencies]
525 | mdurl = ">=0.1,<1.0"
526 |
527 | [package.extras]
528 | benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"]
529 | code-style = ["pre-commit (==2.6)"]
530 | compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"]
531 | linkify = ["linkify-it-py (>=1.0,<2.0)"]
532 | plugins = ["mdit-py-plugins"]
533 | profiling = ["gprof2dot"]
534 | rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
535 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
536 |
537 | [[package]]
538 | name = "markupsafe"
539 | version = "2.1.2"
540 | description = "Safely add untrusted strings to HTML/XML markup."
541 | optional = false
542 | python-versions = ">=3.7"
543 | files = [
544 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
545 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
546 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
547 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
548 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
549 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
550 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
551 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
552 | {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
553 | {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
554 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
555 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
556 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
557 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
558 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
559 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
560 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
561 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
562 | {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
563 | {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
564 | {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
565 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
566 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
567 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
568 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
569 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
570 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
571 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
572 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
573 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
574 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
575 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
576 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
577 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
578 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
579 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
580 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
581 | {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
582 | {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
583 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
584 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
585 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
586 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
587 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
588 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
589 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
590 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
591 | {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
592 | {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
593 | {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
594 | ]
595 |
596 | [[package]]
597 | name = "mccabe"
598 | version = "0.7.0"
599 | description = "McCabe checker, plugin for flake8"
600 | optional = false
601 | python-versions = ">=3.6"
602 | files = [
603 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
604 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
605 | ]
606 |
607 | [[package]]
608 | name = "mdit-py-plugins"
609 | version = "0.3.3"
610 | description = "Collection of plugins for markdown-it-py"
611 | optional = false
612 | python-versions = ">=3.7"
613 | files = [
614 | {file = "mdit-py-plugins-0.3.3.tar.gz", hash = "sha256:5cfd7e7ac582a594e23ba6546a2f406e94e42eb33ae596d0734781261c251260"},
615 | {file = "mdit_py_plugins-0.3.3-py3-none-any.whl", hash = "sha256:36d08a29def19ec43acdcd8ba471d3ebab132e7879d442760d963f19913e04b9"},
616 | ]
617 |
618 | [package.dependencies]
619 | markdown-it-py = ">=1.0.0,<3.0.0"
620 |
621 | [package.extras]
622 | code-style = ["pre-commit"]
623 | rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"]
624 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
625 |
626 | [[package]]
627 | name = "mdurl"
628 | version = "0.1.2"
629 | description = "Markdown URL utilities"
630 | optional = false
631 | python-versions = ">=3.7"
632 | files = [
633 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
634 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
635 | ]
636 |
637 | [[package]]
638 | name = "mypy-extensions"
639 | version = "1.0.0"
640 | description = "Type system extensions for programs checked with the mypy type checker."
641 | optional = false
642 | python-versions = ">=3.5"
643 | files = [
644 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
645 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
646 | ]
647 |
648 | [[package]]
649 | name = "myst-parser"
650 | version = "0.17.2"
651 | description = "An extended commonmark compliant parser, with bridges to docutils & sphinx."
652 | optional = false
653 | python-versions = ">=3.7"
654 | files = [
655 | {file = "myst-parser-0.17.2.tar.gz", hash = "sha256:4c076d649e066f9f5c7c661bae2658be1ca06e76b002bb97f02a09398707686c"},
656 | {file = "myst_parser-0.17.2-py3-none-any.whl", hash = "sha256:1635ce3c18965a528d6de980f989ff64d6a1effb482e1f611b1bfb79e38f3d98"},
657 | ]
658 |
659 | [package.dependencies]
660 | docutils = ">=0.15,<0.18"
661 | jinja2 = "*"
662 | markdown-it-py = ">=1.0.0,<3.0.0"
663 | mdit-py-plugins = ">=0.3.0,<0.4.0"
664 | pyyaml = "*"
665 | sphinx = ">=3.1,<5"
666 | typing-extensions = "*"
667 |
668 | [package.extras]
669 | code-style = ["pre-commit (>=2.12,<3.0)"]
670 | linkify = ["linkify-it-py (>=1.0,<2.0)"]
671 | rtd = ["ipython", "sphinx-book-theme", "sphinx-panels", "sphinxcontrib-bibtex (>=2.4,<3.0)", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"]
672 | testing = ["beautifulsoup4", "coverage", "docutils (>=0.17.0,<0.18.0)", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions"]
673 |
674 | [[package]]
675 | name = "numpy"
676 | version = "2.0.2"
677 | description = "Fundamental package for array computing in Python"
678 | optional = false
679 | python-versions = ">=3.9"
680 | files = [
681 | {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"},
682 | {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"},
683 | {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"},
684 | {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"},
685 | {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"},
686 | {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"},
687 | {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"},
688 | {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"},
689 | {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"},
690 | {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"},
691 | {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"},
692 | {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"},
693 | {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"},
694 | {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"},
695 | {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"},
696 | {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"},
697 | {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"},
698 | {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"},
699 | {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"},
700 | {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"},
701 | {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"},
702 | {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"},
703 | {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"},
704 | {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"},
705 | {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"},
706 | {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"},
707 | {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"},
708 | {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"},
709 | {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"},
710 | {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"},
711 | {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"},
712 | {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"},
713 | {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"},
714 | {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"},
715 | {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"},
716 | {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"},
717 | {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"},
718 | {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"},
719 | {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"},
720 | {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"},
721 | {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"},
722 | {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"},
723 | {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"},
724 | {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"},
725 | {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"},
726 | ]
727 |
728 | [[package]]
729 | name = "packaging"
730 | version = "23.0"
731 | description = "Core utilities for Python packages"
732 | optional = false
733 | python-versions = ">=3.7"
734 | files = [
735 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
736 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
737 | ]
738 |
739 | [[package]]
740 | name = "pathspec"
741 | version = "0.11.0"
742 | description = "Utility library for gitignore style pattern matching of file paths."
743 | optional = false
744 | python-versions = ">=3.7"
745 | files = [
746 | {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
747 | {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
748 | ]
749 |
750 | [[package]]
751 | name = "pillow"
752 | version = "10.3.0"
753 | description = "Python Imaging Library (Fork)"
754 | optional = false
755 | python-versions = ">=3.8"
756 | files = [
757 | {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
758 | {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
759 | {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
760 | {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
761 | {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
762 | {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
763 | {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
764 | {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
765 | {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
766 | {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
767 | {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
768 | {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
769 | {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
770 | {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
771 | {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
772 | {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
773 | {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
774 | {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
775 | {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
776 | {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
777 | {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
778 | {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
779 | {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
780 | {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
781 | {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
782 | {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
783 | {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
784 | {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
785 | {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
786 | {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
787 | {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
788 | {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
789 | {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
790 | {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
791 | {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
792 | {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
793 | {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
794 | {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
795 | {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
796 | {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
797 | {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
798 | {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
799 | {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
800 | {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
801 | {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
802 | {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
803 | {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
804 | {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
805 | {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
806 | {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
807 | {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
808 | {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
809 | {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
810 | {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
811 | {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
812 | {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
813 | {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
814 | {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
815 | {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
816 | {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
817 | {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
818 | {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
819 | {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
820 | {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
821 | {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
822 | {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
823 | {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
824 | {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
825 | {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
826 | ]
827 |
828 | [package.extras]
829 | docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
830 | fpx = ["olefile"]
831 | mic = ["olefile"]
832 | tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
833 | typing = ["typing-extensions"]
834 | xmp = ["defusedxml"]
835 |
836 | [[package]]
837 | name = "platformdirs"
838 | version = "3.0.0"
839 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
840 | optional = false
841 | python-versions = ">=3.7"
842 | files = [
843 | {file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"},
844 | {file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"},
845 | ]
846 |
847 | [package.extras]
848 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
849 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
850 |
851 | [[package]]
852 | name = "pluggy"
853 | version = "1.0.0"
854 | description = "plugin and hook calling mechanisms for python"
855 | optional = false
856 | python-versions = ">=3.6"
857 | files = [
858 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
859 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
860 | ]
861 |
862 | [package.extras]
863 | dev = ["pre-commit", "tox"]
864 | testing = ["pytest", "pytest-benchmark"]
865 |
866 | [[package]]
867 | name = "py"
868 | version = "1.11.0"
869 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
870 | optional = false
871 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
872 | files = [
873 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
874 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
875 | ]
876 |
877 | [[package]]
878 | name = "pycodestyle"
879 | version = "2.11.1"
880 | description = "Python style guide checker"
881 | optional = false
882 | python-versions = ">=3.8"
883 | files = [
884 | {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
885 | {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
886 | ]
887 |
888 | [[package]]
889 | name = "pyelftools"
890 | version = "0.29"
891 | description = "Library for analyzing ELF files and DWARF debugging information"
892 | optional = false
893 | python-versions = "*"
894 | files = [
895 | {file = "pyelftools-0.29-py2.py3-none-any.whl", hash = "sha256:519f38cf412f073b2d7393aa4682b0190fa901f7c3fa0bff2b82d537690c7fc1"},
896 | {file = "pyelftools-0.29.tar.gz", hash = "sha256:ec761596aafa16e282a31de188737e5485552469ac63b60cfcccf22263fd24ff"},
897 | ]
898 |
899 | [[package]]
900 | name = "pyflakes"
901 | version = "3.2.0"
902 | description = "passive checker of Python programs"
903 | optional = false
904 | python-versions = ">=3.8"
905 | files = [
906 | {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
907 | {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
908 | ]
909 |
910 | [[package]]
911 | name = "pygments"
912 | version = "2.14.0"
913 | description = "Pygments is a syntax highlighting package written in Python."
914 | optional = false
915 | python-versions = ">=3.6"
916 | files = [
917 | {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
918 | {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
919 | ]
920 |
921 | [package.extras]
922 | plugins = ["importlib-metadata"]
923 |
924 | [[package]]
925 | name = "pyside6"
926 | version = "6.6.3.1"
927 | description = "Python bindings for the Qt cross-platform application and UI framework"
928 | optional = true
929 | python-versions = "<3.13,>=3.8"
930 | files = [
931 | {file = "PySide6-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:3d2ebb08a7744b59e1270e57f264a9ef5b45fccdc0328a9aeb50d890d6b3f4f2"},
932 | {file = "PySide6-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:35936f06257e5c37ae8993da0cb5a528e5db3ea1fc2bb6b12cdf899a11510966"},
933 | {file = "PySide6-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:f7acd26fe8e1a745ef0be66b49ee49ee8ae50c2a2855d9792db262ebc7916d98"},
934 | {file = "PySide6-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:d993989a10725c856f5b07f25e0664c5059daa92c259549c9df0972b5b0c7935"},
935 | ]
936 |
937 | [package.dependencies]
938 | PySide6-Addons = "6.6.3.1"
939 | PySide6-Essentials = "6.6.3.1"
940 | shiboken6 = "6.6.3.1"
941 |
942 | [[package]]
943 | name = "pyside6-addons"
944 | version = "6.6.3.1"
945 | description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
946 | optional = true
947 | python-versions = "<3.13,>=3.8"
948 | files = [
949 | {file = "PySide6_Addons-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:31135adc521ed6e3fdc8203507e7e9d72424d6b9ebd245d1189d991e90669d6a"},
950 | {file = "PySide6_Addons-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7373479565e5bd963b9662857c40c20768bc0b5853334e2076a62cb039e91f74"},
951 | {file = "PySide6_Addons-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:3abdc1e21de0c6763e5392af5ed8b2349291318ce235e7c310d84a2f9d5001a9"},
952 | {file = "PySide6_Addons-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:d8fbcd726dbf3e713e5d5ccc45ff0e1a9edfe336d7190c96cf7e7c7598681239"},
953 | ]
954 |
955 | [package.dependencies]
956 | PySide6-Essentials = "6.6.3.1"
957 | shiboken6 = "6.6.3.1"
958 |
959 | [[package]]
960 | name = "pyside6-essentials"
961 | version = "6.6.3.1"
962 | description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
963 | optional = true
964 | python-versions = "<3.13,>=3.8"
965 | files = [
966 | {file = "PySide6_Essentials-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:6c16530b63079711783796584b640cc80a347e0b2dc12651aa2877265df7a008"},
967 | {file = "PySide6_Essentials-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1f41f357ce2384576581e76c9c3df1c4fa5b38e347f0bcd0cae7c5bce42a917c"},
968 | {file = "PySide6_Essentials-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:27034525fdbdd21ef21f20fcd7aaf5c2ffe26f2bcf5269a69dd9492dec7e92aa"},
969 | {file = "PySide6_Essentials-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:31f7e70ada44d3cdbe6686670b3df036c720cfeb1dced0f7704e5f5a4be6a764"},
970 | ]
971 |
972 | [package.dependencies]
973 | shiboken6 = "6.6.3.1"
974 |
975 | [[package]]
976 | name = "pytest"
977 | version = "7.2.1"
978 | description = "pytest: simple powerful testing with Python"
979 | optional = false
980 | python-versions = ">=3.7"
981 | files = [
982 | {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"},
983 | {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"},
984 | ]
985 |
986 | [package.dependencies]
987 | attrs = ">=19.2.0"
988 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
989 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
990 | iniconfig = "*"
991 | packaging = "*"
992 | pluggy = ">=0.12,<2.0"
993 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
994 |
995 | [package.extras]
996 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
997 |
998 | [[package]]
999 | name = "pytest-cov"
1000 | version = "3.0.0"
1001 | description = "Pytest plugin for measuring coverage."
1002 | optional = false
1003 | python-versions = ">=3.6"
1004 | files = [
1005 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
1006 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
1007 | ]
1008 |
1009 | [package.dependencies]
1010 | coverage = {version = ">=5.2.1", extras = ["toml"]}
1011 | pytest = ">=4.6"
1012 |
1013 | [package.extras]
1014 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
1015 |
1016 | [[package]]
1017 | name = "pytz"
1018 | version = "2022.7.1"
1019 | description = "World timezone definitions, modern and historical"
1020 | optional = false
1021 | python-versions = "*"
1022 | files = [
1023 | {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"},
1024 | {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"},
1025 | ]
1026 |
1027 | [[package]]
1028 | name = "pyyaml"
1029 | version = "6.0.1"
1030 | description = "YAML parser and emitter for Python"
1031 | optional = false
1032 | python-versions = ">=3.6"
1033 | files = [
1034 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
1035 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
1036 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
1037 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
1038 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
1039 | {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
1040 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
1041 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
1042 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
1043 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
1044 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
1045 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
1046 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
1047 | {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
1048 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
1049 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
1050 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
1051 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
1052 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
1053 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
1054 | {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
1055 | {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
1056 | {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
1057 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
1058 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
1059 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
1060 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
1061 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
1062 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
1063 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
1064 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
1065 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
1066 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
1067 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
1068 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
1069 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
1070 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
1071 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
1072 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
1073 | {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
1074 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
1075 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
1076 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
1077 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
1078 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
1079 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
1080 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
1081 | {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
1082 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
1083 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
1084 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
1085 | ]
1086 |
1087 | [[package]]
1088 | name = "requests"
1089 | version = "2.28.2"
1090 | description = "Python HTTP for Humans."
1091 | optional = false
1092 | python-versions = ">=3.7, <4"
1093 | files = [
1094 | {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
1095 | {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
1096 | ]
1097 |
1098 | [package.dependencies]
1099 | certifi = ">=2017.4.17"
1100 | charset-normalizer = ">=2,<4"
1101 | idna = ">=2.5,<4"
1102 | urllib3 = ">=1.21.1,<1.27"
1103 |
1104 | [package.extras]
1105 | socks = ["PySocks (>=1.5.6,!=1.5.7)"]
1106 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
1107 |
1108 | [[package]]
1109 | name = "retry"
1110 | version = "0.9.2"
1111 | description = "Easy to use retry decorator."
1112 | optional = false
1113 | python-versions = "*"
1114 | files = [
1115 | {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"},
1116 | {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"},
1117 | ]
1118 |
1119 | [package.dependencies]
1120 | decorator = ">=3.4.2"
1121 | py = ">=1.4.26,<2.0.0"
1122 |
1123 | [[package]]
1124 | name = "setuptools"
1125 | version = "68.0.0"
1126 | description = "Easily download, build, install, upgrade, and uninstall Python packages"
1127 | optional = false
1128 | python-versions = ">=3.7"
1129 | files = [
1130 | {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"},
1131 | {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"},
1132 | ]
1133 |
1134 | [package.extras]
1135 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
1136 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
1137 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
1138 |
1139 | [[package]]
1140 | name = "shiboken6"
1141 | version = "6.6.3.1"
1142 | description = "Python/C++ bindings helper module"
1143 | optional = true
1144 | python-versions = "<3.13,>=3.8"
1145 | files = [
1146 | {file = "shiboken6-6.6.3.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:2a8df586aa9eb629388b368d3157893083c5217ed3eb637bf182d1948c823a0f"},
1147 | {file = "shiboken6-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b1aeff0d79d84ddbdc9970144c1bbc3a52fcb45618d1b33d17d57f99f1246d45"},
1148 | {file = "shiboken6-6.6.3.1-cp38-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:902d9e126ac57cc3841cdc50ba38d53948b40cf667538172f253c4ae7b2dcb2c"},
1149 | {file = "shiboken6-6.6.3.1-cp38-abi3-win_amd64.whl", hash = "sha256:88494b5e08a1f235efddbe2b0b225a3a66e07d72b6091fcc2fc5448572453649"},
1150 | ]
1151 |
1152 | [[package]]
1153 | name = "snowballstemmer"
1154 | version = "2.2.0"
1155 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
1156 | optional = false
1157 | python-versions = "*"
1158 | files = [
1159 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
1160 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
1161 | ]
1162 |
1163 | [[package]]
1164 | name = "sphinx"
1165 | version = "4.3.2"
1166 | description = "Python documentation generator"
1167 | optional = false
1168 | python-versions = ">=3.6"
1169 | files = [
1170 | {file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"},
1171 | {file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"},
1172 | ]
1173 |
1174 | [package.dependencies]
1175 | alabaster = ">=0.7,<0.8"
1176 | babel = ">=1.3"
1177 | colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
1178 | docutils = ">=0.14,<0.18"
1179 | imagesize = "*"
1180 | Jinja2 = ">=2.3"
1181 | packaging = "*"
1182 | Pygments = ">=2.0"
1183 | requests = ">=2.5.0"
1184 | setuptools = "*"
1185 | snowballstemmer = ">=1.1"
1186 | sphinxcontrib-applehelp = "*"
1187 | sphinxcontrib-devhelp = "*"
1188 | sphinxcontrib-htmlhelp = ">=2.0.0"
1189 | sphinxcontrib-jsmath = "*"
1190 | sphinxcontrib-qthelp = "*"
1191 | sphinxcontrib-serializinghtml = ">=1.1.5"
1192 |
1193 | [package.extras]
1194 | docs = ["sphinxcontrib-websupport"]
1195 | lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "types-pkg-resources", "types-requests", "types-typed-ast"]
1196 | test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"]
1197 |
1198 | [[package]]
1199 | name = "sphinxcontrib-applehelp"
1200 | version = "1.0.2"
1201 | description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
1202 | optional = false
1203 | python-versions = ">=3.5"
1204 | files = [
1205 | {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},
1206 | {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"},
1207 | ]
1208 |
1209 | [package.extras]
1210 | lint = ["docutils-stubs", "flake8", "mypy"]
1211 | test = ["pytest"]
1212 |
1213 | [[package]]
1214 | name = "sphinxcontrib-devhelp"
1215 | version = "1.0.2"
1216 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
1217 | optional = false
1218 | python-versions = ">=3.5"
1219 | files = [
1220 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
1221 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
1222 | ]
1223 |
1224 | [package.extras]
1225 | lint = ["docutils-stubs", "flake8", "mypy"]
1226 | test = ["pytest"]
1227 |
1228 | [[package]]
1229 | name = "sphinxcontrib-htmlhelp"
1230 | version = "2.0.0"
1231 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
1232 | optional = false
1233 | python-versions = ">=3.6"
1234 | files = [
1235 | {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"},
1236 | {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"},
1237 | ]
1238 |
1239 | [package.extras]
1240 | lint = ["docutils-stubs", "flake8", "mypy"]
1241 | test = ["html5lib", "pytest"]
1242 |
1243 | [[package]]
1244 | name = "sphinxcontrib-jsmath"
1245 | version = "1.0.1"
1246 | description = "A sphinx extension which renders display math in HTML via JavaScript"
1247 | optional = false
1248 | python-versions = ">=3.5"
1249 | files = [
1250 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
1251 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
1252 | ]
1253 |
1254 | [package.extras]
1255 | test = ["flake8", "mypy", "pytest"]
1256 |
1257 | [[package]]
1258 | name = "sphinxcontrib-qthelp"
1259 | version = "1.0.3"
1260 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
1261 | optional = false
1262 | python-versions = ">=3.5"
1263 | files = [
1264 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
1265 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
1266 | ]
1267 |
1268 | [package.extras]
1269 | lint = ["docutils-stubs", "flake8", "mypy"]
1270 | test = ["pytest"]
1271 |
1272 | [[package]]
1273 | name = "sphinxcontrib-serializinghtml"
1274 | version = "1.1.5"
1275 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
1276 | optional = false
1277 | python-versions = ">=3.5"
1278 | files = [
1279 | {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
1280 | {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
1281 | ]
1282 |
1283 | [package.extras]
1284 | lint = ["docutils-stubs", "flake8", "mypy"]
1285 | test = ["pytest"]
1286 |
1287 | [[package]]
1288 | name = "tomli"
1289 | version = "2.0.1"
1290 | description = "A lil' TOML parser"
1291 | optional = false
1292 | python-versions = ">=3.7"
1293 | files = [
1294 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
1295 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
1296 | ]
1297 |
1298 | [[package]]
1299 | name = "typing-extensions"
1300 | version = "4.4.0"
1301 | description = "Backported and Experimental Type Hints for Python 3.7+"
1302 | optional = false
1303 | python-versions = ">=3.7"
1304 | files = [
1305 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
1306 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
1307 | ]
1308 |
1309 | [[package]]
1310 | name = "urllib3"
1311 | version = "1.26.14"
1312 | description = "HTTP library with thread-safe connection pooling, file post, and more."
1313 | optional = false
1314 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
1315 | files = [
1316 | {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"},
1317 | {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"},
1318 | ]
1319 |
1320 | [package.extras]
1321 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
1322 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
1323 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
1324 |
1325 | [[package]]
1326 | name = "xmltodict"
1327 | version = "0.13.0"
1328 | description = "Makes working with XML feel like you are working with JSON"
1329 | optional = false
1330 | python-versions = ">=3.4"
1331 | files = [
1332 | {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"},
1333 | {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"},
1334 | ]
1335 |
1336 | [extras]
1337 | ui = ["PySide6"]
1338 |
1339 | [metadata]
1340 | lock-version = "2.0"
1341 | python-versions = ">=3.9,<3.13"
1342 | content-hash = "d0fe7967602b1728f0ac77a4e2edeeb0d50b9ba9abfbaf1053e6eab2bab0542f"
1343 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "scrcpy-client"
3 | version = "0.4.7"
4 | description = "A client of scrcpy"
5 | authors = ["lengyue "]
6 | readme = "README.md"
7 | repository = "https://github.com/leng-yue/py-scrcpy-client"
8 | license = "MIT"
9 | packages = [
10 | { include = "scrcpy" },
11 | { include = "scrcpy_ui" },
12 | ]
13 |
14 | [tool.poetry.scripts]
15 | py-scrcpy = "scrcpy_ui:main"
16 |
17 | [tool.poetry.dependencies]
18 | # Should match the python versions in .github/workflows/ci.yml and publish.yml
19 | python = ">=3.9,<3.13"
20 | av = "^12"
21 | numpy = "^2"
22 | adbutils = "^2"
23 |
24 | # Optional dependencies for ui
25 | PySide6 = { version = "^6.0.0", optional = true }
26 |
27 | [tool.poetry.extras]
28 | ui = ["PySide6"]
29 |
30 | [tool.poetry.group.dev.dependencies]
31 | flake8 = "^7"
32 | isort = "*"
33 | black = "^22.3.0"
34 | pytest = "^7.1.2"
35 | pytest-cov = "^3.0.0"
36 | Sphinx = "^4.1.2"
37 | myst-parser = "^0.17.2"
38 |
39 | [tool.isort]
40 | profile = "black"
41 |
42 | [build-system]
43 | requires = ["poetry-core>=1.0.0"]
44 | build-backend = "poetry.core.masonry.api"
45 |
--------------------------------------------------------------------------------
/scrcpy/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Python Scrcpy Client's core module
3 | """
4 |
5 | from .const import *
6 | from .core import Client
7 |
--------------------------------------------------------------------------------
/scrcpy/const.py:
--------------------------------------------------------------------------------
1 | """
2 | This module includes all consts used in this project
3 | """
4 |
5 | # Action
6 | ACTION_DOWN = 0
7 | ACTION_UP = 1
8 | ACTION_MOVE = 2
9 |
10 | # KeyCode
11 | KEYCODE_UNKNOWN = 0
12 | KEYCODE_SOFT_LEFT = 1
13 | KEYCODE_SOFT_RIGHT = 2
14 | KEYCODE_HOME = 3
15 | KEYCODE_BACK = 4
16 | KEYCODE_CALL = 5
17 | KEYCODE_ENDCALL = 6
18 | KEYCODE_0 = 7
19 | KEYCODE_1 = 8
20 | KEYCODE_2 = 9
21 | KEYCODE_3 = 10
22 | KEYCODE_4 = 11
23 | KEYCODE_5 = 12
24 | KEYCODE_6 = 13
25 | KEYCODE_7 = 14
26 | KEYCODE_8 = 15
27 | KEYCODE_9 = 16
28 | KEYCODE_STAR = 17
29 | KEYCODE_POUND = 18
30 | KEYCODE_DPAD_UP = 19
31 | KEYCODE_DPAD_DOWN = 20
32 | KEYCODE_DPAD_LEFT = 21
33 | KEYCODE_DPAD_RIGHT = 22
34 | KEYCODE_DPAD_CENTER = 23
35 | KEYCODE_VOLUME_UP = 24
36 | KEYCODE_VOLUME_DOWN = 25
37 | KEYCODE_POWER = 26
38 | KEYCODE_CAMERA = 27
39 | KEYCODE_CLEAR = 28
40 | KEYCODE_A = 29
41 | KEYCODE_B = 30
42 | KEYCODE_C = 31
43 | KEYCODE_D = 32
44 | KEYCODE_E = 33
45 | KEYCODE_F = 34
46 | KEYCODE_G = 35
47 | KEYCODE_H = 36
48 | KEYCODE_I = 37
49 | KEYCODE_J = 38
50 | KEYCODE_K = 39
51 | KEYCODE_L = 40
52 | KEYCODE_M = 41
53 | KEYCODE_N = 42
54 | KEYCODE_O = 43
55 | KEYCODE_P = 44
56 | KEYCODE_Q = 45
57 | KEYCODE_R = 46
58 | KEYCODE_S = 47
59 | KEYCODE_T = 48
60 | KEYCODE_U = 49
61 | KEYCODE_V = 50
62 | KEYCODE_W = 51
63 | KEYCODE_X = 52
64 | KEYCODE_Y = 53
65 | KEYCODE_Z = 54
66 | KEYCODE_COMMA = 55
67 | KEYCODE_PERIOD = 56
68 | KEYCODE_ALT_LEFT = 57
69 | KEYCODE_ALT_RIGHT = 58
70 | KEYCODE_SHIFT_LEFT = 59
71 | KEYCODE_SHIFT_RIGHT = 60
72 | KEYCODE_TAB = 61
73 | KEYCODE_SPACE = 62
74 | KEYCODE_SYM = 63
75 | KEYCODE_EXPLORER = 64
76 | KEYCODE_ENVELOPE = 65
77 | KEYCODE_ENTER = 66
78 | KEYCODE_DEL = 67
79 | KEYCODE_GRAVE = 68
80 | KEYCODE_MINUS = 69
81 | KEYCODE_EQUALS = 70
82 | KEYCODE_LEFT_BRACKET = 71
83 | KEYCODE_RIGHT_BRACKET = 72
84 | KEYCODE_BACKSLASH = 73
85 | KEYCODE_SEMICOLON = 74
86 | KEYCODE_APOSTROPHE = 75
87 | KEYCODE_SLASH = 76
88 | KEYCODE_AT = 77
89 | KEYCODE_NUM = 78
90 | KEYCODE_HEADSETHOOK = 79
91 | KEYCODE_PLUS = 81
92 | KEYCODE_MENU = 82
93 | KEYCODE_NOTIFICATION = 83
94 | KEYCODE_SEARCH = 84
95 | KEYCODE_MEDIA_PLAY_PAUSE = 85
96 | KEYCODE_MEDIA_STOP = 86
97 | KEYCODE_MEDIA_NEXT = 87
98 | KEYCODE_MEDIA_PREVIOUS = 88
99 | KEYCODE_MEDIA_REWIND = 89
100 | KEYCODE_MEDIA_FAST_FORWARD = 90
101 | KEYCODE_MUTE = 91
102 | KEYCODE_PAGE_UP = 92
103 | KEYCODE_PAGE_DOWN = 93
104 | KEYCODE_BUTTON_A = 96
105 | KEYCODE_BUTTON_B = 97
106 | KEYCODE_BUTTON_C = 98
107 | KEYCODE_BUTTON_X = 99
108 | KEYCODE_BUTTON_Y = 100
109 | KEYCODE_BUTTON_Z = 101
110 | KEYCODE_BUTTON_L1 = 102
111 | KEYCODE_BUTTON_R1 = 103
112 | KEYCODE_BUTTON_L2 = 104
113 | KEYCODE_BUTTON_R2 = 105
114 | KEYCODE_BUTTON_THUMBL = 106
115 | KEYCODE_BUTTON_THUMBR = 107
116 | KEYCODE_BUTTON_START = 108
117 | KEYCODE_BUTTON_SELECT = 109
118 | KEYCODE_BUTTON_MODE = 110
119 | KEYCODE_ESCAPE = 111
120 | KEYCODE_FORWARD_DEL = 112
121 | KEYCODE_CTRL_LEFT = 113
122 | KEYCODE_CTRL_RIGHT = 114
123 | KEYCODE_CAPS_LOCK = 115
124 | KEYCODE_SCROLL_LOCK = 116
125 | KEYCODE_META_LEFT = 117
126 | KEYCODE_META_RIGHT = 118
127 | KEYCODE_FUNCTION = 119
128 | KEYCODE_SYSRQ = 120
129 | KEYCODE_BREAK = 121
130 | KEYCODE_MOVE_HOME = 122
131 | KEYCODE_MOVE_END = 123
132 | KEYCODE_INSERT = 124
133 | KEYCODE_FORWARD = 125
134 | KEYCODE_MEDIA_PLAY = 126
135 | KEYCODE_MEDIA_PAUSE = 127
136 | KEYCODE_MEDIA_CLOSE = 128
137 | KEYCODE_MEDIA_EJECT = 129
138 | KEYCODE_MEDIA_RECORD = 130
139 | KEYCODE_F1 = 131
140 | KEYCODE_F2 = 132
141 | KEYCODE_F3 = 133
142 | KEYCODE_F4 = 134
143 | KEYCODE_F5 = 135
144 | KEYCODE_F6 = 136
145 | KEYCODE_F7 = 137
146 | KEYCODE_F8 = 138
147 | KEYCODE_F9 = 139
148 | KEYCODE_F10 = 140
149 | KEYCODE_F11 = 141
150 | KEYCODE_F12 = 142
151 | KEYCODE_NUM_LOCK = 143
152 | KEYCODE_NUMPAD_0 = 144
153 | KEYCODE_NUMPAD_1 = 145
154 | KEYCODE_NUMPAD_2 = 146
155 | KEYCODE_NUMPAD_3 = 147
156 | KEYCODE_NUMPAD_4 = 148
157 | KEYCODE_NUMPAD_5 = 149
158 | KEYCODE_NUMPAD_6 = 150
159 | KEYCODE_NUMPAD_7 = 151
160 | KEYCODE_NUMPAD_8 = 152
161 | KEYCODE_NUMPAD_9 = 153
162 | KEYCODE_NUMPAD_DIVIDE = 154
163 | KEYCODE_NUMPAD_MULTIPLY = 155
164 | KEYCODE_NUMPAD_SUBTRACT = 156
165 | KEYCODE_NUMPAD_ADD = 157
166 | KEYCODE_NUMPAD_DOT = 158
167 | KEYCODE_NUMPAD_COMMA = 159
168 | KEYCODE_NUMPAD_ENTER = 160
169 | KEYCODE_NUMPAD_EQUALS = 161
170 | KEYCODE_NUMPAD_LEFT_PAREN = 162
171 | KEYCODE_NUMPAD_RIGHT_PAREN = 163
172 | KEYCODE_VOLUME_MUTE = 164
173 | KEYCODE_INFO = 165
174 | KEYCODE_CHANNEL_UP = 166
175 | KEYCODE_CHANNEL_DOWN = 167
176 | KEYCODE_ZOOM_IN = 168
177 | KEYCODE_ZOOM_OUT = 169
178 | KEYCODE_TV = 170
179 | KEYCODE_WINDOW = 171
180 | KEYCODE_GUIDE = 172
181 | KEYCODE_DVR = 173
182 | KEYCODE_BOOKMARK = 174
183 | KEYCODE_CAPTIONS = 175
184 | KEYCODE_SETTINGS = 176
185 | KEYCODE_TV_POWER = 177
186 | KEYCODE_TV_INPUT = 178
187 | KEYCODE_STB_POWER = 179
188 | KEYCODE_STB_INPUT = 180
189 | KEYCODE_AVR_POWER = 181
190 | KEYCODE_AVR_INPUT = 182
191 | KEYCODE_PROG_RED = 183
192 | KEYCODE_PROG_GREEN = 184
193 | KEYCODE_PROG_YELLOW = 185
194 | KEYCODE_PROG_BLUE = 186
195 | KEYCODE_APP_SWITCH = 187
196 | KEYCODE_BUTTON_1 = 188
197 | KEYCODE_BUTTON_2 = 189
198 | KEYCODE_BUTTON_3 = 190
199 | KEYCODE_BUTTON_4 = 191
200 | KEYCODE_BUTTON_5 = 192
201 | KEYCODE_BUTTON_6 = 193
202 | KEYCODE_BUTTON_7 = 194
203 | KEYCODE_BUTTON_8 = 195
204 | KEYCODE_BUTTON_9 = 196
205 | KEYCODE_BUTTON_10 = 197
206 | KEYCODE_BUTTON_11 = 198
207 | KEYCODE_BUTTON_12 = 199
208 | KEYCODE_BUTTON_13 = 200
209 | KEYCODE_BUTTON_14 = 201
210 | KEYCODE_BUTTON_15 = 202
211 | KEYCODE_BUTTON_16 = 203
212 | KEYCODE_LANGUAGE_SWITCH = 204
213 | KEYCODE_MANNER_MODE = 205
214 | KEYCODE_3D_MODE = 206
215 | KEYCODE_CONTACTS = 207
216 | KEYCODE_CALENDAR = 208
217 | KEYCODE_MUSIC = 209
218 | KEYCODE_CALCULATOR = 210
219 | KEYCODE_ZENKAKU_HANKAKU = 211
220 | KEYCODE_EISU = 212
221 | KEYCODE_MUHENKAN = 213
222 | KEYCODE_HENKAN = 214
223 | KEYCODE_KATAKANA_HIRAGANA = 215
224 | KEYCODE_YEN = 216
225 | KEYCODE_RO = 217
226 | KEYCODE_KANA = 218
227 | KEYCODE_ASSIST = 219
228 | KEYCODE_BRIGHTNESS_DOWN = 220
229 | KEYCODE_BRIGHTNESS_UP = 221
230 | KEYCODE_MEDIA_AUDIO_TRACK = 222
231 | KEYCODE_SLEEP = 223
232 | KEYCODE_WAKEUP = 224
233 | KEYCODE_PAIRING = 225
234 | KEYCODE_MEDIA_TOP_MENU = 226
235 | KEYCODE_11 = 227
236 | KEYCODE_12 = 228
237 | KEYCODE_LAST_CHANNEL = 229
238 | KEYCODE_TV_DATA_SERVICE = 230
239 | KEYCODE_VOICE_ASSIST = 231
240 | KEYCODE_TV_RADIO_SERVICE = 232
241 | KEYCODE_TV_TELETEXT = 233
242 | KEYCODE_TV_NUMBER_ENTRY = 234
243 | KEYCODE_TV_TERRESTRIAL_ANALOG = 235
244 | KEYCODE_TV_TERRESTRIAL_DIGITAL = 236
245 | KEYCODE_TV_SATELLITE = 237
246 | KEYCODE_TV_SATELLITE_BS = 238
247 | KEYCODE_TV_SATELLITE_CS = 239
248 | KEYCODE_TV_SATELLITE_SERVICE = 240
249 | KEYCODE_TV_NETWORK = 241
250 | KEYCODE_TV_ANTENNA_CABLE = 242
251 | KEYCODE_TV_INPUT_HDMI_1 = 243
252 | KEYCODE_TV_INPUT_HDMI_2 = 244
253 | KEYCODE_TV_INPUT_HDMI_3 = 245
254 | KEYCODE_TV_INPUT_HDMI_4 = 246
255 | KEYCODE_TV_INPUT_COMPOSITE_1 = 247
256 | KEYCODE_TV_INPUT_COMPOSITE_2 = 248
257 | KEYCODE_TV_INPUT_COMPONENT_1 = 249
258 | KEYCODE_TV_INPUT_COMPONENT_2 = 250
259 | KEYCODE_TV_INPUT_VGA_1 = 251
260 | KEYCODE_TV_AUDIO_DESCRIPTION = 252
261 | KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253
262 | KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254
263 | KEYCODE_TV_ZOOM_MODE = 255
264 | KEYCODE_TV_CONTENTS_MENU = 256
265 | KEYCODE_TV_MEDIA_CONTEXT_MENU = 257
266 | KEYCODE_TV_TIMER_PROGRAMMING = 258
267 | KEYCODE_HELP = 259
268 | KEYCODE_NAVIGATE_PREVIOUS = 260
269 | KEYCODE_NAVIGATE_NEXT = 261
270 | KEYCODE_NAVIGATE_IN = 262
271 | KEYCODE_NAVIGATE_OUT = 263
272 | KEYCODE_STEM_PRIMARY = 264
273 | KEYCODE_STEM_1 = 265
274 | KEYCODE_STEM_2 = 266
275 | KEYCODE_STEM_3 = 267
276 | KEYCODE_DPAD_UP_LEFT = 268
277 | KEYCODE_DPAD_DOWN_LEFT = 269
278 | KEYCODE_DPAD_UP_RIGHT = 270
279 | KEYCODE_DPAD_DOWN_RIGHT = 271
280 | KEYCODE_MEDIA_SKIP_FORWARD = 272
281 | KEYCODE_MEDIA_SKIP_BACKWARD = 273
282 | KEYCODE_MEDIA_STEP_FORWARD = 274
283 | KEYCODE_MEDIA_STEP_BACKWARD = 275
284 | KEYCODE_SOFT_SLEEP = 276
285 | KEYCODE_CUT = 277
286 | KEYCODE_COPY = 278
287 | KEYCODE_PASTE = 279
288 | KEYCODE_SYSTEM_NAVIGATION_UP = 280
289 | KEYCODE_SYSTEM_NAVIGATION_DOWN = 281
290 | KEYCODE_SYSTEM_NAVIGATION_LEFT = 282
291 | KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283
292 | KEYCODE_KEYCODE_ALL_APPS = 284
293 | KEYCODE_KEYCODE_REFRESH = 285
294 | KEYCODE_KEYCODE_THUMBS_UP = 286
295 | KEYCODE_KEYCODE_THUMBS_DOWN = 287
296 |
297 | # Event
298 | EVENT_INIT = "init"
299 | EVENT_FRAME = "frame"
300 | EVENT_DISCONNECT = "disconnect"
301 |
302 | # Type
303 | TYPE_INJECT_KEYCODE = 0
304 | TYPE_INJECT_TEXT = 1
305 | TYPE_INJECT_TOUCH_EVENT = 2
306 | TYPE_INJECT_SCROLL_EVENT = 3
307 | TYPE_BACK_OR_SCREEN_ON = 4
308 | TYPE_EXPAND_NOTIFICATION_PANEL = 5
309 | TYPE_EXPAND_SETTINGS_PANEL = 6
310 | TYPE_COLLAPSE_PANELS = 7
311 | TYPE_GET_CLIPBOARD = 8
312 | TYPE_SET_CLIPBOARD = 9
313 | TYPE_SET_SCREEN_POWER_MODE = 10
314 | TYPE_ROTATE_DEVICE = 11
315 |
316 | # Lock screen orientation
317 | LOCK_SCREEN_ORIENTATION_UNLOCKED = -1
318 | LOCK_SCREEN_ORIENTATION_INITIAL = -2
319 | LOCK_SCREEN_ORIENTATION_0 = 0
320 | LOCK_SCREEN_ORIENTATION_1 = 1
321 | LOCK_SCREEN_ORIENTATION_2 = 2
322 | LOCK_SCREEN_ORIENTATION_3 = 3
323 |
324 | # Screen power mode
325 | POWER_MODE_OFF = 0
326 | POWER_MODE_NORMAL = 2
327 |
--------------------------------------------------------------------------------
/scrcpy/control.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import socket
3 | import struct
4 | from time import sleep
5 |
6 | import scrcpy
7 | from scrcpy import const
8 |
9 |
10 | def inject(control_type: int):
11 | """
12 | Inject control code, with this inject, we will be able to do unit test
13 |
14 | Args:
15 | control_type: event to send, TYPE_*
16 | """
17 |
18 | def wrapper(f):
19 | @functools.wraps(f)
20 | def inner(*args, **kwargs):
21 | package = struct.pack(">B", control_type) + f(*args, **kwargs)
22 | if args[0].parent.control_socket is not None:
23 | with args[0].parent.control_socket_lock:
24 | args[0].parent.control_socket.send(package)
25 | return package
26 |
27 | return inner
28 |
29 | return wrapper
30 |
31 |
32 | class ControlSender:
33 | def __init__(self, parent):
34 | self.parent = parent
35 |
36 | @inject(const.TYPE_INJECT_KEYCODE)
37 | def keycode(
38 | self, keycode: int, action: int = const.ACTION_DOWN, repeat: int = 0
39 | ) -> bytes:
40 | """
41 | Send keycode to device
42 |
43 | Args:
44 | keycode: const.KEYCODE_*
45 | action: ACTION_DOWN | ACTION_UP
46 | repeat: repeat count
47 | """
48 | return struct.pack(">Biii", action, keycode, repeat, 0)
49 |
50 | @inject(const.TYPE_INJECT_TEXT)
51 | def text(self, text: str) -> bytes:
52 | """
53 | Send text to device
54 |
55 | Args:
56 | text: text to send
57 | """
58 |
59 | buffer = text.encode("utf-8")
60 | return struct.pack(">i", len(buffer)) + buffer
61 |
62 | @inject(const.TYPE_INJECT_TOUCH_EVENT)
63 | def touch(
64 | self,
65 | x: int,
66 | y: int,
67 | action: int = const.ACTION_DOWN,
68 | touch_id: int = 0x1234567887654321,
69 | ) -> bytes:
70 | """
71 | Touch screen
72 |
73 | Args:
74 | x: horizontal position
75 | y: vertical position
76 | action: ACTION_DOWN | ACTION_UP | ACTION_MOVE
77 | touch_id: Default using virtual id -1, you can specify it to emulate multi finger touch
78 | """
79 | x, y = max(x, 0), max(y, 0)
80 | return struct.pack(
81 | ">BqiiHHHii",
82 | action,
83 | touch_id,
84 | int(x),
85 | int(y),
86 | int(self.parent.resolution[0]),
87 | int(self.parent.resolution[1]),
88 | 0xFFFF,
89 | 1,
90 | 1,
91 | )
92 |
93 | @inject(const.TYPE_INJECT_SCROLL_EVENT)
94 | def scroll(self, x: int, y: int, h: int, v: int) -> bytes:
95 | """
96 | Scroll screen
97 |
98 | Args:
99 | x: horizontal position
100 | y: vertical position
101 | h: horizontal movement
102 | v: vertical movement
103 | """
104 |
105 | x, y = max(x, 0), max(y, 0)
106 | return struct.pack(
107 | ">iiHHii",
108 | int(x),
109 | int(y),
110 | int(self.parent.resolution[0]),
111 | int(self.parent.resolution[1]),
112 | int(h),
113 | int(v),
114 | )
115 |
116 | @inject(const.TYPE_BACK_OR_SCREEN_ON)
117 | def back_or_turn_screen_on(self, action: int = const.ACTION_DOWN) -> bytes:
118 | """
119 | If the screen is off, it is turned on only on ACTION_DOWN
120 |
121 | Args:
122 | action: ACTION_DOWN | ACTION_UP
123 | """
124 | return struct.pack(">B", action)
125 |
126 | @inject(const.TYPE_EXPAND_NOTIFICATION_PANEL)
127 | def expand_notification_panel(self) -> bytes:
128 | """
129 | Expand notification panel
130 | """
131 | return b""
132 |
133 | @inject(const.TYPE_EXPAND_SETTINGS_PANEL)
134 | def expand_settings_panel(self) -> bytes:
135 | """
136 | Expand settings panel
137 | """
138 | return b""
139 |
140 | @inject(const.TYPE_COLLAPSE_PANELS)
141 | def collapse_panels(self) -> bytes:
142 | """
143 | Collapse all panels
144 | """
145 | return b""
146 |
147 | def get_clipboard(self) -> str:
148 | """
149 | Get clipboard
150 | """
151 | # Since this function need socket response, we can't auto inject it any more
152 | s: socket.socket = self.parent.control_socket
153 |
154 | with self.parent.control_socket_lock:
155 | # Flush socket
156 | s.setblocking(False)
157 | while True:
158 | try:
159 | s.recv(1024)
160 | except BlockingIOError:
161 | break
162 | s.setblocking(True)
163 |
164 | # Read package
165 | package = struct.pack(">B", const.TYPE_GET_CLIPBOARD)
166 | s.send(package)
167 | (code,) = struct.unpack(">B", s.recv(1))
168 | assert code == 0
169 | (length,) = struct.unpack(">i", s.recv(4))
170 |
171 | return s.recv(length).decode("utf-8")
172 |
173 | @inject(const.TYPE_SET_CLIPBOARD)
174 | def set_clipboard(self, text: str, paste: bool = False) -> bytes:
175 | """
176 | Set clipboard
177 |
178 | Args:
179 | text: the string you want to set
180 | paste: paste now
181 | """
182 | buffer = text.encode("utf-8")
183 | return struct.pack(">?i", paste, len(buffer)) + buffer
184 |
185 | @inject(const.TYPE_SET_SCREEN_POWER_MODE)
186 | def set_screen_power_mode(self, mode: int = scrcpy.POWER_MODE_NORMAL) -> bytes:
187 | """
188 | Set screen power mode
189 |
190 | Args:
191 | mode: POWER_MODE_OFF | POWER_MODE_NORMAL
192 | """
193 | return struct.pack(">b", mode)
194 |
195 | @inject(const.TYPE_ROTATE_DEVICE)
196 | def rotate_device(self) -> bytes:
197 | """
198 | Rotate device
199 | """
200 | return b""
201 |
202 | def swipe(
203 | self,
204 | start_x: int,
205 | start_y: int,
206 | end_x: int,
207 | end_y: int,
208 | move_step_length: int = 5,
209 | move_steps_delay: float = 0.005,
210 | ) -> None:
211 | """
212 | Swipe on screen
213 |
214 | Args:
215 | start_x: start horizontal position
216 | start_y: start vertical position
217 | end_x: start horizontal position
218 | end_y: end vertical position
219 | move_step_length: length per step
220 | move_steps_delay: sleep seconds after each step
221 | :return:
222 | """
223 |
224 | self.touch(start_x, start_y, const.ACTION_DOWN)
225 | next_x = start_x
226 | next_y = start_y
227 |
228 | if end_x > self.parent.resolution[0]:
229 | end_x = self.parent.resolution[0]
230 |
231 | if end_y > self.parent.resolution[1]:
232 | end_y = self.parent.resolution[1]
233 |
234 | decrease_x = True if start_x > end_x else False
235 | decrease_y = True if start_y > end_y else False
236 | while True:
237 | if decrease_x:
238 | next_x -= move_step_length
239 | if next_x < end_x:
240 | next_x = end_x
241 | else:
242 | next_x += move_step_length
243 | if next_x > end_x:
244 | next_x = end_x
245 |
246 | if decrease_y:
247 | next_y -= move_step_length
248 | if next_y < end_y:
249 | next_y = end_y
250 | else:
251 | next_y += move_step_length
252 | if next_y > end_y:
253 | next_y = end_y
254 |
255 | self.touch(next_x, next_y, const.ACTION_MOVE)
256 |
257 | if next_x == end_x and next_y == end_y:
258 | self.touch(next_x, next_y, const.ACTION_UP)
259 | break
260 | sleep(move_steps_delay)
261 |
--------------------------------------------------------------------------------
/scrcpy/core.py:
--------------------------------------------------------------------------------
1 | import os
2 | import socket
3 | import struct
4 | import threading
5 | import time
6 | from time import sleep
7 | from typing import Any, Callable, Optional, Tuple, Union
8 |
9 | import numpy as np
10 | from adbutils import AdbConnection, AdbDevice, AdbError, Network, adb
11 | from av.codec import CodecContext
12 | from av.error import InvalidDataError
13 |
14 | from .const import (
15 | EVENT_DISCONNECT,
16 | EVENT_FRAME,
17 | EVENT_INIT,
18 | LOCK_SCREEN_ORIENTATION_UNLOCKED,
19 | )
20 | from .control import ControlSender
21 |
22 |
23 | class Client:
24 | def __init__(
25 | self,
26 | device: Optional[Union[AdbDevice, str, any]] = None,
27 | max_width: int = 0,
28 | bitrate: int = 8000000,
29 | max_fps: int = 0,
30 | flip: bool = False,
31 | block_frame: bool = False,
32 | stay_awake: bool = False,
33 | lock_screen_orientation: int = LOCK_SCREEN_ORIENTATION_UNLOCKED,
34 | connection_timeout: int = 3000,
35 | encoder_name: Optional[str] = None,
36 | codec_name: Optional[str] = None,
37 | ):
38 | """
39 | Create a scrcpy client, this client won't be started until you call the start function
40 |
41 | Args:
42 | device: Android device, select first one if none, from serial if str
43 | max_width: frame width that will be broadcast from android server
44 | bitrate: bitrate
45 | max_fps: maximum fps, 0 means not limited (supported after android 10)
46 | flip: flip the video
47 | block_frame: only return nonempty frames
48 | stay_awake: keep Android device awake
49 | lock_screen_orientation: lock screen orientation, LOCK_SCREEN_ORIENTATION_*
50 | connection_timeout: timeout for connection, unit is ms
51 | encoder_name: encoder name, enum: [OMX.google.h264.encoder, OMX.qcom.video.encoder.avc, c2.qti.avc.encoder, c2.android.avc.encoder], default is None (Auto)
52 | codec_name: codec name, enum: [h264, h265, av1], default is None (Auto)
53 | """
54 | # Check Params
55 | assert max_width >= 0, "max_width must be greater than or equal to 0"
56 | assert bitrate >= 0, "bitrate must be greater than or equal to 0"
57 | assert max_fps >= 0, "max_fps must be greater than or equal to 0"
58 | assert (
59 | -1 <= lock_screen_orientation <= 3
60 | ), "lock_screen_orientation must be LOCK_SCREEN_ORIENTATION_*"
61 | assert (
62 | connection_timeout >= 0
63 | ), "connection_timeout must be greater than or equal to 0"
64 | assert encoder_name in [
65 | None,
66 | "OMX.google.h264.encoder",
67 | "OMX.qcom.video.encoder.avc",
68 | "c2.qti.avc.encoder",
69 | "c2.android.avc.encoder",
70 | ]
71 | assert codec_name in [None, "h264", "h265", "av1"]
72 |
73 | # Params
74 | self.flip = flip
75 | self.max_width = max_width
76 | self.bitrate = bitrate
77 | self.max_fps = max_fps
78 | self.block_frame = block_frame
79 | self.stay_awake = stay_awake
80 | self.lock_screen_orientation = lock_screen_orientation
81 | self.connection_timeout = connection_timeout
82 | self.encoder_name = encoder_name
83 | self.codec_name = codec_name
84 |
85 | # Connect to device
86 | if device is None:
87 | device = adb.device_list()[0]
88 | elif isinstance(device, str):
89 | device = adb.device(serial=device)
90 |
91 | self.device = device
92 | self.listeners = dict(frame=[], init=[], disconnect=[])
93 |
94 | # User accessible
95 | self.last_frame: Optional[np.ndarray] = None
96 | self.resolution: Optional[Tuple[int, int]] = None
97 | self.device_name: Optional[str] = None
98 | self.control = ControlSender(self)
99 |
100 | # Need to destroy
101 | self.alive = False
102 | self.__server_stream: Optional[AdbConnection] = None
103 | self.__video_socket: Optional[socket.socket] = None
104 | self.control_socket: Optional[socket.socket] = None
105 | self.control_socket_lock = threading.Lock()
106 |
107 | # Available if start with threaded or daemon_threaded
108 | self.stream_loop_thread = None
109 |
110 | def __init_server_connection(self) -> None:
111 | """
112 | Connect to android server, there will be two sockets, video and control socket.
113 | This method will set: video_socket, control_socket, resolution variables
114 | """
115 | for _ in range(self.connection_timeout // 100):
116 | try:
117 | self.__video_socket = self.device.create_connection(
118 | Network.LOCAL_ABSTRACT, "scrcpy"
119 | )
120 | break
121 | except AdbError:
122 | sleep(0.1)
123 | pass
124 | else:
125 | raise ConnectionError("Failed to connect scrcpy-server after 3 seconds")
126 |
127 | dummy_byte = self.__video_socket.recv(1)
128 | if not len(dummy_byte) or dummy_byte != b"\x00":
129 | raise ConnectionError("Did not receive Dummy Byte!")
130 |
131 | self.control_socket = self.device.create_connection(
132 | Network.LOCAL_ABSTRACT, "scrcpy"
133 | )
134 | self.device_name = self.__video_socket.recv(64).decode("utf-8").rstrip("\x00")
135 | if not len(self.device_name):
136 | raise ConnectionError("Did not receive Device Name!")
137 |
138 | res = self.__video_socket.recv(4)
139 | self.resolution = struct.unpack(">HH", res)
140 | self.__video_socket.setblocking(False)
141 |
142 | def __deploy_server(self) -> None:
143 | """
144 | Deploy server to android device
145 | """
146 | jar_name = "scrcpy-server.jar"
147 | server_file_path = os.path.join(
148 | os.path.abspath(os.path.dirname(__file__)), jar_name
149 | )
150 | self.device.sync.push(server_file_path, f"/data/local/tmp/{jar_name}")
151 | commands = [
152 | f"CLASSPATH=/data/local/tmp/{jar_name}",
153 | "app_process",
154 | "/",
155 | "com.genymobile.scrcpy.Server",
156 | "2.4", # Scrcpy server version
157 | "log_level=info",
158 | f"max_size={self.max_width}",
159 | f"max_fps={self.max_fps}",
160 | f"video_bit_rate={self.bitrate}",
161 | f"video_encoder={self.encoder_name}"
162 | if self.encoder_name
163 | else "video_encoder=OMX.google.h264.encoder",
164 | f"video_codec={self.codec_name}" if self.codec_name else "video_codec=h264",
165 | "tunnel_forward=true",
166 | "send_frame_meta=false",
167 | "control=true",
168 | "audio=false",
169 | "show_touches=false",
170 | "stay_awake=false",
171 | "power_off_on_close=false",
172 | "clipboard_autosync=false",
173 | ]
174 |
175 | self.__server_stream: AdbConnection = self.device.shell(
176 | commands,
177 | stream=True,
178 | )
179 |
180 | # Wait for server to start
181 | self.__server_stream.read(10)
182 |
183 | def start(self, threaded: bool = False, daemon_threaded: bool = False) -> None:
184 | """
185 | Start listening video stream
186 |
187 | Args:
188 | threaded: Run stream loop in a different thread to avoid blocking
189 | daemon_threaded: Run stream loop in a daemon thread to avoid blocking
190 | """
191 | assert self.alive is False
192 |
193 | self.__deploy_server()
194 | self.__init_server_connection()
195 | self.alive = True
196 | self.__send_to_listeners(EVENT_INIT)
197 |
198 | if threaded or daemon_threaded:
199 | self.stream_loop_thread = threading.Thread(
200 | target=self.__stream_loop, daemon=daemon_threaded
201 | )
202 | self.stream_loop_thread.start()
203 | else:
204 | self.__stream_loop()
205 |
206 | def stop(self) -> None:
207 | """
208 | Stop listening (both threaded and blocked)
209 | """
210 | self.alive = False
211 | if self.__server_stream is not None:
212 | try:
213 | self.__server_stream.close()
214 | except Exception:
215 | pass
216 |
217 | if self.control_socket is not None:
218 | try:
219 | self.control_socket.close()
220 | except Exception:
221 | pass
222 |
223 | if self.__video_socket is not None:
224 | try:
225 | self.__video_socket.close()
226 | except Exception:
227 | pass
228 |
229 | def __stream_loop(self) -> None:
230 | """
231 | Core loop for video parsing
232 | """
233 | codec = CodecContext.create("h264", "r")
234 | while self.alive:
235 | try:
236 | raw_h264 = self.__video_socket.recv(0x10000)
237 | if raw_h264 == b"":
238 | raise ConnectionError("Video stream is disconnected")
239 | packets = codec.parse(raw_h264)
240 | for packet in packets:
241 | frames = codec.decode(packet)
242 | for frame in frames:
243 | frame = frame.to_ndarray(format="bgr24")
244 | if self.flip:
245 | frame = frame[:, ::-1, :]
246 | self.last_frame = frame
247 | self.resolution = (frame.shape[1], frame.shape[0])
248 | self.__send_to_listeners(EVENT_FRAME, frame)
249 | except (BlockingIOError, InvalidDataError):
250 | time.sleep(0.01)
251 | if not self.block_frame:
252 | self.__send_to_listeners(EVENT_FRAME, None)
253 | except (ConnectionError, OSError) as e: # Socket Closed
254 | if self.alive:
255 | self.__send_to_listeners(EVENT_DISCONNECT)
256 | self.stop()
257 | raise e
258 |
259 | def add_listener(self, cls: str, listener: Callable[..., Any]) -> None:
260 | """
261 | Add a video listener
262 |
263 | Args:
264 | cls: Listener category, support: init, frame
265 | listener: A function to receive frame np.ndarray
266 | """
267 | self.listeners[cls].append(listener)
268 |
269 | def remove_listener(self, cls: str, listener: Callable[..., Any]) -> None:
270 | """
271 | Remove a video listener
272 |
273 | Args:
274 | cls: Listener category, support: init, frame
275 | listener: A function to receive frame np.ndarray
276 | """
277 | self.listeners[cls].remove(listener)
278 |
279 | def __send_to_listeners(self, cls: str, *args, **kwargs) -> None:
280 | """
281 | Send event to listeners
282 |
283 | Args:
284 | cls: Listener type
285 | *args: Other arguments
286 | *kwargs: Other arguments
287 | """
288 | for fun in self.listeners[cls]:
289 | fun(*args, **kwargs)
290 |
--------------------------------------------------------------------------------
/scrcpy/scrcpy-server.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leng-yue/py-scrcpy-client/ad57b15934c71cb24555bbb3f5050b1d6c908de5/scrcpy/scrcpy-server.jar
--------------------------------------------------------------------------------
/scrcpy_ui/__init__.py:
--------------------------------------------------------------------------------
1 | from .main import main
2 |
3 | __all__ = ["main"]
4 |
--------------------------------------------------------------------------------
/scrcpy_ui/main.py:
--------------------------------------------------------------------------------
1 | from argparse import ArgumentParser
2 | from typing import Optional
3 |
4 | from adbutils import adb
5 | from PySide6.QtGui import QImage, QKeyEvent, QMouseEvent, QPixmap, Qt
6 | from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
7 | from ui_main import Ui_MainWindow
8 |
9 | import scrcpy
10 |
11 | if not QApplication.instance():
12 | app = QApplication([])
13 | else:
14 | app = QApplication.instance()
15 |
16 |
17 | class MainWindow(QMainWindow):
18 | def __init__(
19 | self,
20 | max_width: Optional[int],
21 | serial: Optional[str] = None,
22 | encoder_name: Optional[str] = None,
23 | ):
24 | super(MainWindow, self).__init__()
25 | self.ui = Ui_MainWindow()
26 | self.ui.setupUi(self)
27 | self.max_width = max_width
28 |
29 | # Setup devices
30 | self.devices = self.list_devices()
31 | if serial:
32 | self.choose_device(serial)
33 | self.device = adb.device(serial=self.ui.combo_device.currentText())
34 | self.alive = True
35 |
36 | # Setup client
37 | self.client = scrcpy.Client(
38 | device=self.device,
39 | flip=self.ui.flip.isChecked(),
40 | bitrate=1000000000,
41 | encoder_name=encoder_name,
42 | max_fps=60,
43 | )
44 | self.client.add_listener(scrcpy.EVENT_INIT, self.on_init)
45 | self.client.add_listener(scrcpy.EVENT_FRAME, self.on_frame)
46 |
47 | # Bind controllers
48 | self.ui.button_home.clicked.connect(self.on_click_home)
49 | self.ui.button_back.clicked.connect(self.on_click_back)
50 |
51 | # Bind config
52 | self.ui.combo_device.currentTextChanged.connect(self.choose_device)
53 | self.ui.flip.stateChanged.connect(self.on_flip)
54 |
55 | # Bind mouse event
56 | self.ui.label.mousePressEvent = self.on_mouse_event(scrcpy.ACTION_DOWN)
57 | self.ui.label.mouseMoveEvent = self.on_mouse_event(scrcpy.ACTION_MOVE)
58 | self.ui.label.mouseReleaseEvent = self.on_mouse_event(scrcpy.ACTION_UP)
59 |
60 | # Keyboard event
61 | self.keyPressEvent = self.on_key_event(scrcpy.ACTION_DOWN)
62 | self.keyReleaseEvent = self.on_key_event(scrcpy.ACTION_UP)
63 |
64 | def choose_device(self, device):
65 | if device not in self.devices:
66 | msgBox = QMessageBox()
67 | msgBox.setText(f"Device serial [{device}] not found!")
68 | msgBox.exec()
69 | return
70 |
71 | # Ensure text
72 | self.ui.combo_device.setCurrentText(device)
73 | # Restart service
74 | if getattr(self, "client", None):
75 | self.client.stop()
76 | self.client.device = adb.device(serial=device)
77 |
78 | def list_devices(self):
79 | self.ui.combo_device.clear()
80 | items = [i.serial for i in adb.device_list()]
81 | self.ui.combo_device.addItems(items)
82 | return items
83 |
84 | def on_flip(self, _):
85 | self.client.flip = self.ui.flip.isChecked()
86 |
87 | def on_click_home(self):
88 | self.client.control.keycode(scrcpy.KEYCODE_HOME, scrcpy.ACTION_DOWN)
89 | self.client.control.keycode(scrcpy.KEYCODE_HOME, scrcpy.ACTION_UP)
90 |
91 | def on_click_back(self):
92 | self.client.control.back_or_turn_screen_on(scrcpy.ACTION_DOWN)
93 | self.client.control.back_or_turn_screen_on(scrcpy.ACTION_UP)
94 |
95 | def on_mouse_event(self, action=scrcpy.ACTION_DOWN):
96 | def handler(evt: QMouseEvent):
97 | focused_widget = QApplication.focusWidget()
98 | if focused_widget is not None:
99 | focused_widget.clearFocus()
100 | ratio = self.max_width / max(self.client.resolution)
101 | self.client.control.touch(
102 | evt.position().x() / ratio, evt.position().y() / ratio, action
103 | )
104 |
105 | return handler
106 |
107 | def on_key_event(self, action=scrcpy.ACTION_DOWN):
108 | def handler(evt: QKeyEvent):
109 | code = self.map_code(evt.key())
110 | if code != -1:
111 | self.client.control.keycode(code, action)
112 |
113 | return handler
114 |
115 | def map_code(self, code):
116 | """
117 | Map qt keycode ti android keycode
118 |
119 | Args:
120 | code: qt keycode
121 | android keycode, -1 if not founded
122 | """
123 |
124 | if code == -1:
125 | return -1
126 | if 48 <= code <= 57:
127 | return code - 48 + 7
128 | if 65 <= code <= 90:
129 | return code - 65 + 29
130 | if 97 <= code <= 122:
131 | return code - 97 + 29
132 |
133 | hard_code = {
134 | 32: scrcpy.KEYCODE_SPACE,
135 | 16777219: scrcpy.KEYCODE_DEL,
136 | 16777248: scrcpy.KEYCODE_SHIFT_LEFT,
137 | 16777220: scrcpy.KEYCODE_ENTER,
138 | 16777217: scrcpy.KEYCODE_TAB,
139 | 16777249: scrcpy.KEYCODE_CTRL_LEFT,
140 | }
141 | if code in hard_code:
142 | return hard_code[code]
143 |
144 | print(f"Unknown keycode: {code}")
145 | return -1
146 |
147 | def on_init(self):
148 | self.setWindowTitle(f"Serial: {self.client.device_name}")
149 |
150 | def on_frame(self, frame):
151 | app.processEvents()
152 | if frame is not None:
153 | ratio = self.max_width / max(self.client.resolution)
154 | image = QImage(
155 | frame,
156 | frame.shape[1],
157 | frame.shape[0],
158 | frame.shape[1] * 3,
159 | QImage.Format_BGR888,
160 | )
161 | pix = QPixmap(image)
162 | pix.setDevicePixelRatio(1 / ratio)
163 | self.ui.label.setPixmap(pix)
164 | self.resize(1, 1)
165 |
166 | def closeEvent(self, _):
167 | self.client.stop()
168 | self.alive = False
169 |
170 |
171 | def main():
172 | parser = ArgumentParser(description="A simple scrcpy client")
173 | parser.add_argument(
174 | "-m",
175 | "--max_width",
176 | type=int,
177 | default=800,
178 | help="Set max width of the window, default 800",
179 | )
180 | parser.add_argument(
181 | "-d",
182 | "--device",
183 | type=str,
184 | help="Select device manually (device serial required)",
185 | )
186 | parser.add_argument("--encoder_name", type=str, help="Encoder name to use")
187 | args = parser.parse_args()
188 |
189 | m = MainWindow(args.max_width, args.device, args.encoder_name)
190 | m.show()
191 |
192 | m.client.start()
193 | while m.alive:
194 | m.client.start()
195 |
196 |
197 | if __name__ == "__main__":
198 | main()
199 |
--------------------------------------------------------------------------------
/scrcpy_ui/main.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 523
10 | 566
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 | -
19 |
20 |
21 | QLayout::SetFixedSize
22 |
23 |
-
24 |
25 |
-
26 |
27 |
28 | Qt::Horizontal
29 |
30 |
31 |
32 | 40
33 | 20
34 |
35 |
36 |
37 |
38 | -
39 |
40 |
41 | Device
42 |
43 |
44 |
45 | -
46 |
47 |
48 |
49 | 100
50 | 0
51 |
52 |
53 |
54 |
55 | -
56 |
57 |
58 | Flip
59 |
60 |
61 |
62 | -
63 |
64 |
65 | Qt::Horizontal
66 |
67 |
68 |
69 | 40
70 | 20
71 |
72 |
73 |
74 |
75 |
76 |
77 | -
78 |
79 |
80 | QLayout::SetFixedSize
81 |
82 |
83 | 0
84 |
85 |
-
86 |
87 |
88 | <html><head/><body><p><span style=" font-size:20pt;">Loading</span></p></body></html>
89 |
90 |
91 | Qt::AlignCenter
92 |
93 |
94 |
95 |
96 |
97 | -
98 |
99 |
100 | 6
101 |
102 |
103 | QLayout::SetFixedSize
104 |
105 |
-
106 |
107 |
108 | Qt::Horizontal
109 |
110 |
111 |
112 | 40
113 | 20
114 |
115 |
116 |
117 |
118 | -
119 |
120 |
121 | HOME
122 |
123 |
124 |
125 | -
126 |
127 |
128 | BACK
129 |
130 |
131 |
132 | -
133 |
134 |
135 | Qt::Horizontal
136 |
137 |
138 |
139 | 40
140 | 20
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/scrcpy_ui/ui_main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'main.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 6.1.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide6.QtCore import * # type: ignore
12 | from PySide6.QtGui import * # type: ignore
13 | from PySide6.QtWidgets import * # type: ignore
14 |
15 |
16 | class Ui_MainWindow(object):
17 | def setupUi(self, MainWindow):
18 | if not MainWindow.objectName():
19 | MainWindow.setObjectName("MainWindow")
20 | MainWindow.resize(523, 566)
21 | self.centralwidget = QWidget(MainWindow)
22 | self.centralwidget.setObjectName("centralwidget")
23 | self.horizontalLayout_3 = QHBoxLayout(self.centralwidget)
24 | self.horizontalLayout_3.setObjectName("horizontalLayout_3")
25 | self.verticalLayout = QVBoxLayout()
26 | self.verticalLayout.setObjectName("verticalLayout")
27 | self.verticalLayout.setSizeConstraint(QLayout.SetFixedSize)
28 | self.horizontalLayout_4 = QHBoxLayout()
29 | self.horizontalLayout_4.setObjectName("horizontalLayout_4")
30 | self.horizontalSpacer_3 = QSpacerItem(
31 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
32 | )
33 |
34 | self.horizontalLayout_4.addItem(self.horizontalSpacer_3)
35 |
36 | self.label_2 = QLabel(self.centralwidget)
37 | self.label_2.setObjectName("label_2")
38 |
39 | self.horizontalLayout_4.addWidget(self.label_2)
40 |
41 | self.combo_device = QComboBox(self.centralwidget)
42 | self.combo_device.setObjectName("combo_device")
43 | self.combo_device.setMinimumSize(QSize(100, 0))
44 |
45 | self.horizontalLayout_4.addWidget(self.combo_device)
46 |
47 | self.flip = QCheckBox(self.centralwidget)
48 | self.flip.setObjectName("flip")
49 |
50 | self.horizontalLayout_4.addWidget(self.flip)
51 |
52 | self.horizontalSpacer_4 = QSpacerItem(
53 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
54 | )
55 |
56 | self.horizontalLayout_4.addItem(self.horizontalSpacer_4)
57 |
58 | self.verticalLayout.addLayout(self.horizontalLayout_4)
59 |
60 | self.horizontalLayout_2 = QHBoxLayout()
61 | self.horizontalLayout_2.setObjectName("horizontalLayout_2")
62 | self.horizontalLayout_2.setSizeConstraint(QLayout.SetFixedSize)
63 | self.horizontalLayout_2.setContentsMargins(-1, -1, 0, -1)
64 | self.label = QLabel(self.centralwidget)
65 | self.label.setObjectName("label")
66 | self.label.setAlignment(Qt.AlignCenter)
67 |
68 | self.horizontalLayout_2.addWidget(self.label)
69 |
70 | self.verticalLayout.addLayout(self.horizontalLayout_2)
71 |
72 | self.horizontalLayout = QHBoxLayout()
73 | self.horizontalLayout.setSpacing(6)
74 | self.horizontalLayout.setObjectName("horizontalLayout")
75 | self.horizontalLayout.setSizeConstraint(QLayout.SetFixedSize)
76 | self.horizontalSpacer = QSpacerItem(
77 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
78 | )
79 |
80 | self.horizontalLayout.addItem(self.horizontalSpacer)
81 |
82 | self.button_home = QPushButton(self.centralwidget)
83 | self.button_home.setObjectName("button_home")
84 |
85 | self.horizontalLayout.addWidget(self.button_home)
86 |
87 | self.button_back = QPushButton(self.centralwidget)
88 | self.button_back.setObjectName("button_back")
89 |
90 | self.horizontalLayout.addWidget(self.button_back)
91 |
92 | self.horizontalSpacer_2 = QSpacerItem(
93 | 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum
94 | )
95 |
96 | self.horizontalLayout.addItem(self.horizontalSpacer_2)
97 |
98 | self.verticalLayout.addLayout(self.horizontalLayout)
99 |
100 | self.verticalLayout.setStretch(1, 100)
101 |
102 | self.horizontalLayout_3.addLayout(self.verticalLayout)
103 |
104 | MainWindow.setCentralWidget(self.centralwidget)
105 |
106 | self.retranslateUi(MainWindow)
107 |
108 | QMetaObject.connectSlotsByName(MainWindow)
109 |
110 | # setupUi
111 |
112 | def retranslateUi(self, MainWindow):
113 | MainWindow.setWindowTitle(
114 | QCoreApplication.translate("MainWindow", "MainWindow", None)
115 | )
116 | self.label_2.setText(QCoreApplication.translate("MainWindow", "Device", None))
117 | self.flip.setText(QCoreApplication.translate("MainWindow", "Flip", None))
118 | self.label.setText(
119 | QCoreApplication.translate(
120 | "MainWindow",
121 | 'Loading
',
122 | None,
123 | )
124 | )
125 | self.button_home.setText(QCoreApplication.translate("MainWindow", "HOME", None))
126 | self.button_back.setText(QCoreApplication.translate("MainWindow", "BACK", None))
127 |
128 | # retranslateUi
129 |
--------------------------------------------------------------------------------
/scripts/check.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | source_dirs = "scrcpy tests scripts scrcpy_ui"
4 | subprocess.check_call(f"isort --check --diff {source_dirs}", shell=True)
5 | subprocess.check_call(f"black --check --diff {source_dirs}", shell=True)
6 | subprocess.check_call(
7 | f"flake8 --ignore W503,E203,E501,E731,F403,F401 {source_dirs} --exclude scrcpy_ui/ui_main.py",
8 | shell=True,
9 | )
10 |
--------------------------------------------------------------------------------
/scripts/lint.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 |
3 | source_dirs = "scrcpy tests scripts scrcpy_ui"
4 | subprocess.check_call(f"isort {source_dirs}", shell=True)
5 | subprocess.check_call(f"black {source_dirs}", shell=True)
6 |
--------------------------------------------------------------------------------
/tests/test_control.py:
--------------------------------------------------------------------------------
1 | """Tests adapted from https://github.com/Genymobile/scrcpy/blob/v2.4/app/tests/test_control_msg_serialize.c
2 |
3 | make sure to compare to the version we are targeting ^^^^
4 |
5 | See https://github.com/Genymobile/scrcpy/issues/673#issuecomment-516360374
6 | """
7 |
8 |
9 | import threading
10 |
11 | import scrcpy
12 | from scrcpy.control import ControlSender
13 | from tests.utils import FakeStream
14 |
15 |
16 | class MockParent:
17 | class FakeSocket:
18 | def send(self, data):
19 | pass
20 |
21 | resolution = (1920, 1080)
22 |
23 | def __init__(self):
24 | self.control_socket_lock = threading.Lock()
25 | self.control_socket = FakeStream()
26 |
27 |
28 | control = ControlSender(MockParent())
29 |
30 |
31 | def test_control_keycode():
32 | assert control.keycode(scrcpy.KEYCODE_HOME, scrcpy.ACTION_DOWN, 1) == (
33 | b"\x00" # TYPE_INJECT_KEYCODE
34 | + b"\x00" # ACTION_DOWN
35 | + b"\x00\x00\x00\x03" # KEYCODE_HOME
36 | + b"\x00\x00\x00\x01" # Repeat = 1
37 | + b"\x00\x00\x00\x00" # MetaState = 0
38 | )
39 |
40 |
41 | def test_control_touch():
42 | assert control.touch(100, 200, scrcpy.ACTION_DOWN) == (
43 | b"\x02" # TYPE_INJECT_TOUCH_EVENT
44 | + b"\x00" # ACTION_DOWN
45 | + b"\x12\x34\x56\x78\x87\x65\x43\x21" # pointer id
46 | + b"\x00\x00\x00\x64" # X: 100
47 | + b"\x00\x00\x00\xc8" # Y: 200
48 | + b"\x07\x80\x04\x38" # Resolution: (1920, 1080)
49 | + b"\xff\xff" # Pressure: 100%
50 | + b"\x00\x00\x00\x01" # AMOTION_EVENT_BUTTON_PRIMARY (action button)
51 | + b"\x00\x00\x00\x01" # AMOTION_EVENT_BUTTON_PRIMARY (buttons)
52 | )
53 |
54 |
55 | def test_control_text():
56 | text = "hello, world"
57 | assert control.text(text) == (
58 | b"\x01" # TYPE_INJECT_TEXT
59 | + b"\x00\x00\x00\x0c" # Length: 12
60 | + text.encode("utf-8")
61 | )
62 |
63 |
64 | def test_control_scroll():
65 | assert control.scroll(100, 200, 100, 200) == (
66 | b"\x03" # TYPE_INJECT_SCROLL_EVENT
67 | + b"\x00\x00\x00\x64" # X: 100
68 | + b"\x00\x00\x00\xc8" # Y: 200
69 | + b"\x07\x80\x04\x38" # Resolution: (1920, 1080)
70 | + b"\x00\x00\x00\x64" # H: 100
71 | + b"\x00\x00\x00\xc8" # V: 200
72 | )
73 |
74 |
75 | def test_back_or_turn_screen_on():
76 | assert control.back_or_turn_screen_on() == (
77 | b"\x04" + b"\x00" # TYPE_BACK_OR_SCREEN_ON, ACTION_DOWN
78 | )
79 |
80 |
81 | def test_panels():
82 | assert control.expand_notification_panel() == b"\x05"
83 | assert control.expand_settings_panel() == b"\x06"
84 | assert control.collapse_panels() == b"\x07"
85 |
86 |
87 | def test_get_clipboard():
88 | class MockClipboardParent:
89 | class MockSocket:
90 | @staticmethod
91 | def setblocking(_):
92 | pass
93 |
94 | @staticmethod
95 | def send(_):
96 | pass
97 |
98 | @staticmethod
99 | def recv(b):
100 | if b == 1024:
101 | raise BlockingIOError()
102 | elif b == 1:
103 | return b"\x00"
104 | elif b == 4:
105 | return b"\x00\x00\x00\x05"
106 | return b"test0"
107 |
108 | control_socket = MockSocket()
109 | control_socket_lock = threading.Lock()
110 |
111 | assert ControlSender(MockClipboardParent()).get_clipboard() == "test0"
112 |
113 |
114 | def test_set_clipboard():
115 | text = "hello, world"
116 | assert control.set_clipboard(text, False) == (
117 | b"\x09" # TYPE_SET_CLIPBOARD
118 | + b"\x00" # Paste: false
119 | + b"\x00\x00\x00\x0c" # Length: 12
120 | + text.encode("utf-8")
121 | )
122 | assert control.set_clipboard(text, True) == (
123 | b"\x09" # TYPE_SET_CLIPBOARD
124 | + b"\x01" # Paste: true
125 | + b"\x00\x00\x00\x0c" # Length: 12
126 | + text.encode("utf-8")
127 | )
128 |
129 |
130 | def test_set_screen_power_mode():
131 | assert control.set_screen_power_mode(scrcpy.POWER_MODE_NORMAL) == (
132 | b"\x0a" + b"\x02" # TYPE_SET_SCREEN_POWER_MODE, POWER_MODE_NORMAL
133 | )
134 |
135 |
136 | def rotate_device():
137 | assert control.rotate_device() == b"\x0b" # TYPE_ROTATE_DEVICE
138 |
139 |
140 | def test_swipe():
141 | control.swipe(100, 200, 300, 400)
142 | control.swipe(100, 200, 2000, 2000)
143 | control.swipe(300, 400, 100, 200)
144 | control.swipe(2000, 2000, 100, 200)
145 | control.swipe(2000, 2000, -100, -200)
146 | control.swipe(100, 200, 2010, 2010, move_step_length=100)
147 |
--------------------------------------------------------------------------------
/tests/test_core.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 | import pickle
3 |
4 | import pytest
5 | from adbutils import AdbError
6 |
7 | from scrcpy import Client
8 | from tests.utils import FakeStream
9 |
10 |
11 | class Sync:
12 | @staticmethod
13 | def push(a, b):
14 | pass
15 |
16 |
17 | class FakeADBDevice:
18 |
19 | sync = Sync()
20 |
21 | def __init__(self, data, wait=0):
22 | self.data = data
23 | self.__wait = wait
24 |
25 | @staticmethod
26 | def shell(a, stream=True):
27 | return FakeStream([b"\x00" * 128])
28 |
29 | def create_connection(self, a, b):
30 | if self.__wait > 0:
31 | self.__wait -= 1
32 | raise AdbError()
33 |
34 | return FakeStream(self.data.pop(0))
35 |
36 |
37 | def test_connection():
38 | client = Client(
39 | device=FakeADBDevice([[b"\x00", b"test", b"\x07\x80\x04\x38"], []], wait=3)
40 | )
41 | client.start(threaded=True)
42 | client.stop()
43 |
44 | with pytest.raises(ConnectionError):
45 | client = Client(
46 | device=FakeADBDevice(
47 | [[b"\x00", b"test", b"\x07\x80\x04\x38"], []], wait=1000
48 | ),
49 | connection_timeout=1000,
50 | )
51 | client.start(threaded=True)
52 | client.stop()
53 |
54 | # No Dummy Bytes Error
55 | with pytest.raises(ConnectionError) as e:
56 | client = Client(
57 | device=FakeADBDevice([[b"\x01", b"test", b"\x07\x80\x04\x38"], []])
58 | )
59 | client.start(threaded=True)
60 | client.stop()
61 | assert "Dummy Byte" in str(e.value)
62 |
63 | # No Device Name Error
64 | with pytest.raises(ConnectionError) as e:
65 | client = Client(device=FakeADBDevice([[b"\x00", b"", b"\x07\x80\x04\x38"], []]))
66 | client.start(threaded=True)
67 | client.stop()
68 | assert "Device Name" in str(e.value)
69 |
70 |
71 | def test_init_listener():
72 | def on_init():
73 | assert client.device_name == "test"
74 | client.stop()
75 |
76 | client = Client(device=FakeADBDevice([[b"\x00", b"test", b"\x07\x80\x04\x38"], []]))
77 |
78 | client.add_listener("init", on_init)
79 | assert client.listeners["init"] == [on_init]
80 | client.remove_listener("init", on_init)
81 | assert client.listeners["init"] == []
82 |
83 | client.add_listener("init", on_init)
84 | client.start(threaded=True)
85 |
86 |
87 | def test_parse_video():
88 | def on_frame(frame):
89 | frames.append(frame)
90 | if len(frames) == 5:
91 | client.stop()
92 |
93 | # Load test data
94 | video_data = pickle.load(
95 | (pathlib.Path(__file__).parent / "test_video_data.pkl").resolve().open("rb")
96 | )
97 | data = [
98 | [b"\x00", b"test", b"\x07\x80\x04\x38", None] + video_data + [b"OSError"],
99 | [],
100 | ]
101 | frames = []
102 |
103 | # Create client
104 | client = Client(device=FakeADBDevice(data), flip=True)
105 | client.add_listener("frame", on_frame)
106 | with pytest.raises(OSError):
107 | client.start()
108 |
109 | # Wait frames
110 | assert frames[0] is None
111 | assert frames[1].shape == (800, 368, 3)
112 | assert frames[2].shape == (800, 368, 3)
113 |
--------------------------------------------------------------------------------
/tests/test_video_data.pkl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leng-yue/py-scrcpy-client/ad57b15934c71cb24555bbb3f5050b1d6c908de5/tests/test_video_data.pkl
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | class FakeStream:
2 | def __init__(self, data=None):
3 | if data is None:
4 | data = []
5 | self.data = data
6 | self.die = False
7 |
8 | def recv(self, a):
9 | if self.die:
10 | raise OSError()
11 | if len(self.data) == 0:
12 | raise BlockingIOError()
13 | val = self.data.pop(0)
14 | if val is None:
15 | raise BlockingIOError()
16 | if val == b"OSError":
17 | raise OSError()
18 | return val
19 |
20 | def read(self, a):
21 | return self.recv(a)
22 |
23 | def check_okay(self):
24 | return
25 |
26 | @staticmethod
27 | def setblocking(a):
28 | pass
29 |
30 | def close(self):
31 | self.die = True
32 |
33 | def send(self, x):
34 | pass
35 |
--------------------------------------------------------------------------------