├── .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 | ![demo gif](https://raw.githubusercontent.com/leng-yue/py-scrcpy-client/main/demo.gif) 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 | --------------------------------------------------------------------------------