diff --git a/.builds/debian.yml b/.builds/debian.yml new file mode 100644 index 0000000..ee3e060 --- /dev/null +++ b/.builds/debian.yml @@ -0,0 +1,38 @@ +image: debian/buster +sources: + - https://git.sr.ht/~cedric/stegano +packages: + - python3 + - python3-dev + - python3-pip + - python3-venv + - rustc + - cargo + - libffi-dev + - libssl-dev + - libjpeg-dev + - libpng-dev + - libfreetype6-dev +environment: + project: stegano +tasks: + - dependencies: | + pip3 install --user poetry + export PATH="$PATH:/home/build/.local/bin" + cd ${project} + poetry install + - lint: | + export PATH="$PATH:/home/build/.local/bin" + cd ${project} + # stop the build if there are Python syntax errors or undefined names + poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. + poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - test: | + export PATH="$PATH:/home/build/.local/bin" + cd ${project} + poetry run nose2 -v --pretty-assert + - typecheck: | + export PATH="$PATH:/home/build/.local/bin" + cd ${project} + poetry run python tools/run_mypy.py diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 1342eae..0000000 --- a/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true -end_of_line = lf -charset = utf-8 -max_line_length = 88 - -[*.{yml,yaml,json,js,css,html}] -indent_size = 2 - -[*.{md,rst}] -trim_trailing_whitespace = false diff --git a/.github/workflows/pythonapp.yaml b/.github/workflows/pythonapp.yaml deleted file mode 100644 index a59b226..0000000 --- a/.github/workflows/pythonapp.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Python application - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12"] - - steps: - - uses: actions/checkout@v1 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - pip install poetry - poetry install --with dev - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - poetry run flake8 stegano --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - poetry run flake8 stegano --count --max-complexity=18 --ignore=E203 --max-line-length=127 --statistics - - - name: Test with pytest - run: | - poetry run nose2 -v --pretty-assert - env: - testing: actions - - # - name: Type check with mypy - # run: | - # poetry run mypy . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 64f381b..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,27 +0,0 @@ -on: - release: - types: - - published - -name: release - -jobs: - pypi-publish: - name: Upload release to PyPI - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/Stegano - - permissions: - id-token: write # IMPORTANT: this permission is mandatory for trusted publishing - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install Poetry - run: python -m pip install --upgrade pip poetry - - name: Build artifacts - run: poetry build - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index e814e8f..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,36 +0,0 @@ -ci: - autoupdate_schedule: monthly -repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: ["--py37-plus"] - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear - - flake8-implicit-str-concat - args: ["--max-line-length=125", "--ignore=E203"] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 - hooks: - - id: fix-byte-order-marker - - id: trailing-whitespace - exclude: .md - - id: end-of-file-fixer - exclude: tests/.* - - repo: https://github.com/pypa/pip-audit - rev: v2.9.0 - hooks: - - id: pip-audit diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 9712e40..0000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the version of Python and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.11" - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -# We recommend specifying your dependencies to enable reproducible builds: -# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: docs/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e3beb66..16328d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,259 +1,187 @@ ## Release History -### 2.0.0 (2025-06-22) - -- Added functions for hiding/revealing messages in PCM encoded .wav files. - ([#54](https://github.com/cedricbonhomme/Stegano/pull/54)) -- Improved typing. -- Updated dependencies. - - -### 1.0.1 (2025-05-03) - -- Improved the packaging configuration for the command line (stegano.console). - - -### 1.0.0 (2025-04-26) - -- Updated dependencies. -- Improved the packaging configuration. -- Fixed typing errors. - - -### 0.11.5 (2025-02-13) - -- Updated dependencies. -- Aligned pyproject.toml with the standard specification. -- Publishing to PyPI using a Trusted Publisher. - - -### 0.11.4 (2024-09-07) - -- Added a parameter, close_file, to lsb.reveal in order to - specify if the file must be closed at the end of the processing. - - -### 0.11.3 (2024-01-02) - -- Stegano now supports Python 3.12. Support of Python 3.8 has been removed. - - -### 0.11.2 (2023-05-23) - -- improved typing of various functions; -- updated dependencies. - - -### 0.11.1 (2022-11-20) - -- Fixed a bug in the command line when no sub-command is specified. - - -### 0.11.0 (2022-11-20) - -- Reduced memory footprint and processing speed, - the modules ``lsb`` and ``lsbset`` have been merged - ([PR #34](https://github.com/cedricbonhomme/Stegano/pull/34)). - - -### 0.10.2 (2022-01-13) - -- Stegano now uses Pillow 9.0.0 (CVE-2022-22815). - - -### 0.10.1 (2021-11-30) - -- Stegano now uses OpenCV Python 4.5.4 abd Numpy 1.21.4. - - -### 0.10.0 (2021-11-29) - -- new: Implemented Shi-Tomashi corner generator - ([PR #32](https://github.com/cedricbonhomme/Stegano/pull/32)). - Implemented by thundersparkf (see CONTRIBUTORS.md file). - ### 0.9.9 (2021-07-02) -- Stegano now uses Pillow 8.3.0. +* stegano now uses Pillow 8.3.0. ### 0.9.8 (2019-12-20) -- Stegano is now using poetry; -- minor improvements to the command line. +* stegano is now using poetry; +* minor improvements to the command line. ### 0.9.7 (2019-10-27) -- fixed markdown of the previous release. +* fixed markdown of the previous release. ### 0.9.6 (2019-10-27) -- fixed markdown of the previous release; +* fixed markdown of the previous release; ### 0.9.5 (2019-10-27) -- updated dependencies; -- home page of the project is now: https://github.com/cedricbonhomme/Stegano +* updated dependencies; +* home page of the project is now: https://git.sr.ht/~cedric/Stegano ### 0.9.4 (2019-06-05) -- new: Implemented LFSR generator (with tests and CLI) +* new: Implemented LFSR generator (with tests and CLI) ([PR #27](https://github.com/cedricbonhomme/Stegano/pull/27)) -- new: Implemented Ackermann generators CLI interface +* new: Implemented Ackermann generators CLI interface ([PR #26](https://github.com/cedricbonhomme/Stegano/pull/26)) -- new: The Ackermann functions are not actual generators +* new: The Ackermann functions are not actual generators ([#24](https://github.com/cedricbonhomme/Stegano/issues/24)) -- new: add a shift parameter for the lsbmodule +* new: add a shift parameter for the lsbmodule ([#25](https://github.com/cedricbonhomme/Stegano/issues/25)) -- fix: lsbset.hide cause .png transparent area lost +* fix: lsbset.hide cause .png transparent area lost ([#23](https://github.com/cedricbonhomme/Stegano/issues/23)) ### 0.9.3 (2019-04-10) -- it is now possible to either pass the location of an image or directly pass +* it is now possible to either pass the location of an image or directly pass an already opened Image.Image to the hide and reveal methods; -- code re-formatted a bit with black. +* code re-formatted a bit with black. ### 0.9.2 (2019-04-04) -- updated Pillow dependency to version 6.0.0 in order to fix a bug when opening +* updated Pillow dependency to version 6.0.0 in order to fix a bug when opening some PNG files (https://github.com/python-pillow/Pillow/issues/3557). ### 0.9.1 (2019-03-06) -- updated Pillow dependency in order to fix a bug when opening some PNG files. +* updated Pillow dependency in order to fix a bug when opening some PNG files. ### 0.9.0 (2018-12-18) -- added the possibility to shift the encoded bits when using the lsbset module. +* added the possibility to shift the encoded bits when using the lsbset module. ### 0.8.6 (2018-11-05) -- fixed a potential security issue related to CVE-2018-18074. +* fixed a potential security issue related to CVE-2018-18074. ### 0.8.5 (2018-04-18) -- Fixed an encoding problem which occured on Windows during the installation +* Fixed an encoding problem which occured on Windows during the installation of the module. ### 0.8.4 (2018-02-28) -- Stegano is ready for use with pipenv and pipsi. +* Stegano is ready for use with pipenv and pipsi. ### 0.8.3 (2018-02-23) -- the recommended way to install Stegano is now to use pipenv. +* the recommended way to install Stegano is now to use pipenv. ### 0.8.2 (2017-12-20) -- Fixed a bug with the new 'encoding' function when using Stegano as a command +* Fixed a bug with the new 'encoding' function when using Stegano as a command line tool. No default value was set. Default value is UTF-8. ### 0.8.1 (2017-05-16) -- it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message +* it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message to hide/reveal through the command line; -- the help of the command line now displays the available choices for the +* the help of the command line now displays the available choices for the arguments, if it is necessary (list of available encodings, list of available generators); -- tests expected results lies now in a dedicated folder; -- a script has been added in order to get proper exit code check for mypy. +* tests expected results lies now in a dedicated folder; +* a script has been added in order to get proper exit code check for mypy. ### 0.8 (2017-05-06) -- updated command line. All commands are now prefixed with *stegano-*; -- improved type hints; -- it is possible to load and save images from and to file objects (BytesIO); -- improved checks when revealing a message with the lsbset module fails. +* updated command line. All commands are now prefixed with *stegano-*; +* improved type hints; +* it is possible to load and save images from and to file objects (BytesIO); +* improved checks when revealing a message with the lsbset module fails. ### 0.7.1 (2017-05-05) -- improved generators for the lsb-set module; -- improved tests for the generators; -- improved type hints. +* improved generators for the lsb-set module; +* improved tests for the generators; +* improved type hints. ### 0.7 (2017-05-04) -- unicode is now supported. By default UTF-8 encoding is used. UTF-32LE can also +* unicode is now supported. By default UTF-8 encoding is used. UTF-32LE can also be used to hide non-ASCII characters. UTF-8 (8 bits) is the default choice since it is possible to hide longer messages with it. -- improved checks with type hints. +* improved checks with type hints. ### 0.6.9 (2017-03-10) -- introduces some type hints (PEP 484); -- more tests for the generators and for the tools module; -- updated descriptions of generators; -- fixed a bug with a generator that has been previously renamed. +* introduces some type hints (PEP 484); +* more tests for the generators and for the tools module; +* updated descriptions of generators; +* fixed a bug with a generator that has been previously renamed. ### 0.6.8 (2017-03-08) -- bugfix: fixed #12: Error when revealing a hidden binary file in an image. +* bugfix: fixed #12: Error when revealing a hidden binary file in an image. ### 0.6.7 (2017-02-21) -- bugfix: added missing dependency in the setup.py file. +* bugfix: added missing dependency in the setup.py file. ### 0.6.6 (2017-02-20) -- improved docstrings for the desciption of the generators; -- improved the command which displays the list of generators. +* improved docstrings for the desciption of the generators; +* improved the command which displays the list of generators. ### 0.6.5 (2017-02-16) -- added a command to list all available generators for the lsb-set module; -- test when the data image is coming via byte stream, for the lsb module. +* added a command to list all available generators for the lsb-set module; +* test when the data image is coming via byte stream, for the lsb module. ### 0.6.4 (2017-02-06) -- a command line for the 'red' module has been added; -- bugfix: fixed a bug in the lsb-set command line when the generator wasn't +* a command line for the 'red' module has been added; +* bugfix: fixed a bug in the lsb-set command line when the generator wasn't specified by the user. ### 0.6.3 (2017-01-29) -- Support for transparent PNG images has been added (lsb and lsbset modules). +* Support for transparent PNG images has been added (lsb and lsbset modules). ### 0.6.2 (2017-01-19) -- bugfix: solved a bug when the image data is coming via byte streams (ByteIO), +* bugfix: solved a bug when the image data is coming via byte streams (ByteIO), for the exifHeader hiding method. ### 0.6.1 (2016-08-25) -- reorganization of the steganalysis sub-module. +* reorganization of the steganalysis sub-module. ### 0.6 (2016-08-04) -- improvements of the command line of Stéganô. The use of Stéganô through the +* improvements of the command line of Stéganô. The use of Stéganô through the command line has slightly changed ('hide' and 'reveal' are now sub-parameters of the command line). No changes if you use Stéganô as a module in your software. The documentation has been updated accordingly. @@ -261,66 +189,66 @@ ### 0.5.5 (2016-08-03) -- bugfix: Incorrect padding size in `base642string` in tools.base642binary(). +* bugfix: Incorrect padding size in `base642string` in tools.base642binary(). ### 0.5.4 (2016-05-22) -- the generator provided to the functions lsbset.hide() and lsbset.reveal() is +* the generator provided to the functions lsbset.hide() and lsbset.reveal() is now a function. This is more convenient for a user who wants to use a custom generator (not in the module lsbset.generators). -- performance improvements for the lsb and lsbset modules. +* performance improvements for the lsb and lsbset modules. ### 0.5.3 (2016-05-19) -- reorganization of all modules. No impact for the users of Stegano. +* reorganization of all modules. No impact for the users of Stegano. ### 0.5.2 (2016-05-18) -- improvements and bug fixes for the exifHeader module; -- added unit tests for the exifHeader module; -- improvements of the documentation. +* improvements and bug fixes for the exifHeader module; +* added unit tests for the exifHeader module; +* improvements of the documentation. ### 0.5.1 (2016-04-16) -- minor improvements and bug fixes; -- added unit tests for the slsb and slsbset modules. +* minor improvements and bug fixes; +* added unit tests for the slsb and slsbset modules. ### 0.5 (2016-03-18) -- management of greyscale images. +* management of greyscale images. ### 0.4.6 (2016-03-12) -- bugfix when the length of the message to hide is not divisible by 3, +* bugfix when the length of the message to hide is not divisible by 3, for the slsb and slsbset module. ### 0.4.5 (2015-12-23) -- bugfix. +* bugfix. ### 0.4.4 (2015-12-23) -- new project home page; -- minor updated to the documentation. +* new project home page; +* minor updated to the documentation. ### 0.4.3 (2015-10-06) -- bug fixes for Python 3; -- bug fixes in the scripts in *./bin*. +* bug fixes for Python 3; +* bug fixes in the scripts in *./bin*. ### 0.4.2 (2015-10-05) -- first stable release on PypI. +* first stable release on PypI. ### 0.4 (2012-01-02) @@ -334,16 +262,16 @@ Python codes as a Python module or as a program in your scripts. ### 0.3 (2011-04-15) -- you can now use Stéganô as a library in your Python program; +* you can now use Stéganô as a library in your Python program; (python setup.py install) or as a 'program' thanks to the scripts provided in the bin directory; -- new documentation (reStructuredText) comes with Stéganô. +* new documentation (reStructuredText) comes with Stéganô. ### 0.2 (2011-03-24) -- this release introduces some bugfixes and a major speed improvement of the +* this release introduces some bugfixes and a major speed improvement of the *reveal* function for the LSB method. Moreover it is now possible to hide a binary file (ogg, executable, etc.); -- a new technique for hiding/revealing a message in a JPEG picture by using the +* a new technique for hiding/revealing a message in a JPEG picture by using the description field of the image is provided. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 719e518..1652742 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,17 +7,12 @@ ## Contributors -- Alexander Treml - https://github.com/AlexanderTreml - Adrien Cosson - https://cosson.io - Andrew Roberts -- Christophe Goessen - https://github.com/cgoessen -- Flavien Roux - https://github.com/FlavienRx - Maxwell Gerber - https://github.com/maxwellgerber -- Mickaël Schoentgen - Nejdet Çağdaş Yücesoy - panni - Peter Justin -- thundersparkf - https://github.com/thundersparkf - +- Christophe Goessen - https://github.com/cgoessen And thank you to the testers! diff --git a/COPYING b/COPYING index 94a9ed0..20d40b6 100755 --- a/COPYING +++ b/COPYING @@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/README.md b/README.md index 3b10733..b59c26b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Stegano -[![Workflow](https://github.com/cedricbonhomme/Stegano/workflows/Python%20application/badge.svg?style=flat-square)](https://github.com/cedricbonhomme/Stegano/actions?query=workflow%3A%22Python+application%22) +[![builds.sr.ht status](https://builds.sr.ht/~cedric/stegano.svg)](https://builds.sr.ht/~cedric/stegano) -[Stegano](https://github.com/cedricbonhomme/Stegano), a pure Python Steganography + +[Stegano](https://sr.ht/~cedric/stegano), a pure Python Steganography module. Steganography is the art and science of writing hidden messages in such a way @@ -11,6 +12,9 @@ existence of the message, a form of security through obscurity. Consequently, functions provided by Stegano only hide messages, without encryption. Steganography is often used with cryptography. +For reporting issues, visit the tracker here: +https://todo.sr.ht/~cedric/stegano + ## Installation @@ -64,7 +68,7 @@ Secret Message ### Hide the message with the Sieve of Eratosthenes ```bash -$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'Secret Message' --generator eratosthenes -o Lena2.png +$ stegano-lsb-set hide -i ./tests/sample-files/Lenna.png -m 'Secret Message' --generator eratosthenes -o Lena2.png ``` The message will be scattered in the picture, following a set described by the @@ -83,7 +87,7 @@ $ python -m unittest discover -v Running the static type checker: ```bash -$ mypy stegano +$ python tools/run_mypy.py ``` @@ -93,23 +97,11 @@ Contributions are welcome. If you want to contribute to Stegano I highly recommend you to install it in a Python virtual environment with poetry. -## Donations - -If you wish and if you like Stegano, you can donate via GitHub Sponsors: - -[![GitHub Sponsors](https://img.shields.io/github/sponsors/cedricbonhomme)](https://github.com/sponsors/cedricbonhomme) - -or with Bitcoin to this address: -bc1q56u6sj7cvlwu58v5lemljcvkh7v2gc3tv8mj0e - -Thank you ! - - ## License This software is licensed under [GNU General Public License version 3](https://www.gnu.org/licenses/gpl-3.0.html) -Copyright (C) 2010-2025 [Cédric Bonhomme](https://www.cedricbonhomme.org) +Copyright (C) 2010-2021 [Cédric Bonhomme](https://www.cedricbonhomme.org) For more information, [the list of authors and contributors](CONTRIBUTORS.md) is available. diff --git a/bin/__init__.py b/bin/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bin/__init__.py @@ -0,0 +1 @@ + diff --git a/bin/lsb.py b/bin/lsb.py new file mode 100755 index 0000000..f6da980 --- /dev/null +++ b/bin/lsb.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Stegano - Stegano is a pure Python steganography module. +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org +# +# For more information : https://github.com/cedricbonhomme/Stegano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +__author__ = "Cedric Bonhomme" +__version__ = "$Revision: 0.8 $" +__date__ = "$Date: 2016/08/04 $" +__revision__ = "$Date: 2019/06/01 $" +__license__ = "GPLv3" + +import argparse + +try: + from stegano import lsb +except: + print("Install Stegano: pipx install Stegano") + +from stegano import tools + +def main(): + parser = argparse.ArgumentParser(prog='stegano-lsb') + subparsers = parser.add_subparsers(help='sub-command help', dest='command') + + # Subparser: Hide + parser_hide = subparsers.add_parser('hide', help='hide help') + # Original image + parser_hide.add_argument("-i", "--input", dest="input_image_file", + required=True, help="Input image file.") + parser_hide.add_argument("-e", "--encoding", dest="encoding", + choices=tools.ENCODINGS.keys(), default='UTF-8', + help="Specify the encoding of the message to hide." + + " UTF-8 (default) or UTF-32LE.") + + group_secret = parser_hide.add_mutually_exclusive_group(required=True) + # Non binary secret message to hide + group_secret.add_argument("-m", dest="secret_message", + help="Your secret message to hide (non binary).") + # Binary secret message to hide + group_secret.add_argument("-f", dest="secret_file", + help="Your secret to hide (Text or any binary file).") + + # Image containing the secret + parser_hide.add_argument("-o", "--output", dest="output_image_file", + required=True, help="Output image containing the secret.") + + # Shift the message to hide + parser_hide.add_argument("-s", "--shift", dest="shift", default=0, + help="Shift for the message to hide") + + # Subparser: Reveal + parser_reveal = subparsers.add_parser('reveal', help='reveal help') + parser_reveal.add_argument("-i", "--input", dest="input_image_file", + required=True, help="Input image file.") + parser_reveal.add_argument("-e", "--encoding", dest="encoding", + choices=tools.ENCODINGS.keys(), default='UTF-8', + help="Specify the encoding of the message to reveal." + + " UTF-8 (default) or UTF-32LE.") + parser_reveal.add_argument("-o", dest="secret_binary", + help="Output for the binary secret (Text or any binary file).") + # Shift the message to reveal + parser_reveal.add_argument("-s", "--shift", dest="shift", default=0, + help="Shift for the reveal") + + arguments = parser.parse_args() + + + if arguments.command == 'hide': + if arguments.secret_message != None: + secret = arguments.secret_message + elif arguments.secret_file != None: + secret = tools.binary2base64(arguments.secret_file) + + img_encoded = lsb.hide(arguments.input_image_file, secret, + arguments.encoding, int(arguments.shift)) + try: + img_encoded.save(arguments.output_image_file) + except Exception as e: + # If hide() returns an error (Too long message). + print(e) + + elif arguments.command == 'reveal': + secret = lsb.reveal(arguments.input_image_file, arguments.encoding, + int(arguments.shift)) + if arguments.secret_binary != None: + data = tools.base642binary(secret) + with open(arguments.secret_binary, "wb") as f: + f.write(data) + else: + print(secret) diff --git a/bin/lsbset.py b/bin/lsbset.py new file mode 100755 index 0000000..046f488 --- /dev/null +++ b/bin/lsbset.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Stegano - Stegano is a pure Python steganography module. +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org +# +# For more information : https://github.com/cedricbonhomme/Stegano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +__author__ = "Cedric Bonhomme" +__version__ = "$Revision: 0.7 $" +__date__ = "$Date: 2016/03/18 $" +__revision__ = "$Date: 2019/06/04 $" +__license__ = "GPLv3" + +import inspect +import crayons + +try: + from stegano import lsbset + from stegano.lsbset import generators +except: + print("Install stegano: pipx install Stegano") + +from stegano import tools + +import argparse + + + +class ValidateGenerator(argparse.Action): + def __call__(self, parser, args, values, option_string=None): + valid_generators = [generator[0] for generator in inspect.getmembers( + generators, inspect.isfunction)] + # Verify that the generator is valid + generator = values[0] + if generator not in valid_generators: + raise ValueError("Unknown generator: %s" % generator) + # Set the generator_function arg of the parser + setattr(args, self.dest, values) + +def main(): + parser = argparse.ArgumentParser(prog='stegano-lsb-set') + subparsers = parser.add_subparsers(help='sub-command help', dest='command') + + # Subparser: Hide + parser_hide = subparsers.add_parser('hide', help='hide help') + # Original image + parser_hide.add_argument("-i", "--input", dest="input_image_file", + required=True, help="Input image file.") + parser_hide.add_argument("-e", "--encoding", dest="encoding", + choices=tools.ENCODINGS.keys(), default='UTF-8', + help="Specify the encoding of the message to hide." + + " UTF-8 (default) or UTF-32LE.") + + # Generator + parser_hide.add_argument("-g", "--generator", dest="generator_function", + action=ValidateGenerator, + nargs='*', required=True, help="Generator (with optional arguments)") + parser_hide.add_argument("-s", "--shift", dest="shift", + default=0, help="Shift for the generator") + + group_secret = parser_hide.add_mutually_exclusive_group(required=True) + # Non binary secret message to hide + group_secret.add_argument("-m", dest="secret_message", + help="Your secret message to hide (non binary).") + # Binary secret message to hide + group_secret.add_argument("-f", dest="secret_file", + help="Your secret to hide (Text or any binary file).") + + # Image containing the secret + parser_hide.add_argument("-o", "--output", dest="output_image_file", + required=True, help="Output image containing the secret.") + + + # Subparser: Reveal + parser_reveal = subparsers.add_parser('reveal', help='reveal help') + parser_reveal.add_argument("-i", "--input", dest="input_image_file", + required=True, help="Input image file.") + parser_reveal.add_argument("-e", "--encoding", dest="encoding", + choices=tools.ENCODINGS.keys(), default='UTF-8', + help="Specify the encoding of the message to reveal." + + " UTF-8 (default) or UTF-32LE.") + parser_reveal.add_argument("-g", "--generator", dest="generator_function", + action=ValidateGenerator, + nargs='*', required=True, help="Generator (with optional arguments)") + parser_reveal.add_argument("-s", "--shift", dest="shift", + default=0, help="Shift for the generator") + parser_reveal.add_argument("-o", dest="secret_binary", + help="Output for the binary secret (Text or any binary file).") + + + # Subparser: List generators + parser_list_generators = subparsers.add_parser('list-generators', + help='list-generators help') + + arguments = parser.parse_args() + + if arguments.command != 'list-generators': + try: + arguments.generator_function[0] + except AttributeError: + print('You must specify the name of a generator.') + parser.print_help() + exit(1) + + try: + if (arguments.generator_function[0] == "LFSR"): + # Compute the size of the image for use by the LFSR generator if needed + tmp = tools.open_image(arguments.input_image_file) + size = tmp.width * tmp.height + tmp.close() + arguments.generator_function.append(size) + if (len(arguments.generator_function) > 1): + generator = getattr(generators, arguments.generator_function[0])( + *[int(e) for e in arguments.generator_function[1:]]) + else: + generator = getattr(generators, arguments.generator_function[0])() + + except AttributeError as e: + print("Unknown generator: {}".format(arguments.generator_function)) + exit(1) + + if arguments.command == 'hide': + if arguments.secret_message != None: + secret = arguments.secret_message + elif arguments.secret_file != "": + secret = tools.binary2base64(arguments.secret_file) + + img_encoded = lsbset.hide(arguments.input_image_file, secret, generator, + int(arguments.shift)) + try: + img_encoded.save(arguments.output_image_file) + except Exception as e: + # If hide() returns an error (Too long message). + print(e) + + elif arguments.command == 'reveal': + try: + secret = lsbset.reveal(arguments.input_image_file, generator, + int(arguments.shift)) + except IndexError: + print("Impossible to detect message.") + exit(0) + if arguments.secret_binary != None: + data = tools.base642binary(secret) + with open(arguments.secret_binary, "w") as f: + f.write(data) + else: + print(secret) + + elif arguments.command == 'list-generators': + all_generators = inspect.getmembers(generators, inspect.isfunction) + for generator in all_generators: + print('Generator id:') + print(' {}'.format(crayons.green(generator[0], bold=True))) + print('Desciption:') + print(' {}'.format(generator[1].__doc__)) diff --git a/stegano/console/parity.py b/bin/parity.py similarity index 73% rename from stegano/console/parity.py rename to bin/parity.py index 7c0ea8e..de24ff6 100644 --- a/stegano/console/parity.py +++ b/bin/parity.py @@ -1,6 +1,8 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # # For more information : https://github.com/cedricbonhomme/Stegano # @@ -22,32 +24,21 @@ __version__ = "$Revision: 0.7 $" __date__ = "$Date: 2016/08/25 $" __license__ = "GPLv3" -import argparse - from PIL import Image +import argparse + try: from stegano.steganalysis import parity -except Exception: +except: print("Install Stegano: pipx install Stegano") - def main(): - parser = argparse.ArgumentParser(prog="stegano-steganalysis-parity") - parser.add_argument( - "-i", - "--input", - dest="input_image_file", - required=True, - help="Input image file.", - ) - parser.add_argument( - "-o", - "--output", - dest="output_image_file", - required=True, - help="Output image file.", - ) + parser = argparse.ArgumentParser(prog='stegano-steganalysis-parity') + parser.add_argument("-i", "--input", dest="input_image_file", + required=True, help="Input image file.") + parser.add_argument("-o", "--output", dest="output_image_file", + required=True, help="Output image file.") arguments = parser.parse_args() input_image_file = Image.open(arguments.input_image_file) diff --git a/stegano/console/red.py b/bin/red.py similarity index 58% rename from stegano/console/red.py rename to bin/red.py index f656e2b..839dc0c 100644 --- a/stegano/console/red.py +++ b/bin/red.py @@ -1,6 +1,8 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # # For more information : https://github.com/cedricbonhomme/Stegano # @@ -26,36 +28,32 @@ import argparse try: from stegano import red -except Exception: +except: print("Install stegano: sudo pip install Stegano") def main(): - parser = argparse.ArgumentParser(prog="stegano-red") - subparsers = parser.add_subparsers(help="sub-command help", dest="command") + parser = argparse.ArgumentParser(prog='stegano-red') + subparsers = parser.add_subparsers(help='sub-command help', dest='command') - parser_hide = subparsers.add_parser("hide", help="hide help") - parser_hide.add_argument( - "-i", "--input", dest="input_image_file", help="Image file" - ) - parser_hide.add_argument( - "-m", dest="secret_message", help="Your secret message to hide (non binary)." - ) - parser_hide.add_argument( - "-o", "--output", dest="output_image_file", help="Image file" - ) + parser_hide = subparsers.add_parser('hide', help='hide help') + parser_hide.add_argument("-i", "--input", dest="input_image_file", + help="Image file") + parser_hide.add_argument("-m", dest="secret_message", + help="Your secret message to hide (non binary).") + parser_hide.add_argument("-o", "--output", dest="output_image_file", + help="Image file") - parser_reveal = subparsers.add_parser("reveal", help="reveal help") - parser_reveal.add_argument( - "-i", "--input", dest="input_image_file", help="Image file" - ) + parser_reveal = subparsers.add_parser('reveal', help='reveal help') + parser_reveal.add_argument("-i", "--input", dest="input_image_file", + help="Image file") arguments = parser.parse_args() - if arguments.command == "hide": + if arguments.command == 'hide': secret = red.hide(arguments.input_image_file, arguments.secret_message) secret.save(arguments.output_image_file) - elif arguments.command == "reveal": + elif arguments.command == 'reveal': secret = red.reveal(arguments.input_image_file) print(secret) diff --git a/stegano/console/statistics.py b/bin/statistics.py similarity index 85% rename from stegano/console/statistics.py rename to bin/statistics.py index 85b144e..735c4a5 100644 --- a/stegano/console/statistics.py +++ b/bin/statistics.py @@ -1,6 +1,8 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # # For more information : https://github.com/cedricbonhomme/Stegano # @@ -23,20 +25,22 @@ __date__ = "$Date: 2016/08/26 $" __revision__ = "$Date: 2016/08/26 $" __license__ = "GPLv3" -import argparse - from PIL import Image +import argparse + try: from stegano.steganalysis import statistics -except Exception: +except: print("Install Stegano: sudo pip install Stegano") def main(): - parser = argparse.ArgumentParser(prog="stegano-steganalysis-parity") - parser.add_argument("-i", "--input", dest="input_image_file", help="Image file") - parser.add_argument("-o", "--output", dest="output_image_file", help="Image file") + parser = argparse.ArgumentParser(prog='stegano-steganalysis-parity') + parser.add_argument("-i", "--input", dest="input_image_file", + help="Image file") + parser.add_argument("-o", "--output", dest="output_image_file", + help="Image file") arguments = parser.parse_args() input_image_file = Image.open(arguments.input_image_file) diff --git a/docs/conf.py b/docs/conf.py index 48f4e7a..335242b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # # Stéganô documentation build configuration file, created by # sphinx-quickstart on Wed Jul 25 13:33:39 2012. @@ -9,162 +10,169 @@ # # All configuration values have a default; values that are commented out # serve to show the default. + +import sys, os + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) +#sys.path.insert(0, os.path.abspath('.')) + # -- General configuration ----------------------------------------------------- + # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +#needs_sphinx = '1.0' + # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +templates_path = ['_templates'] # The suffix of source filenames. -source_suffix = ".rst" +source_suffix = '.rst' # The encoding of source files. -# source_encoding = 'utf-8-sig' +#source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = "index" +master_doc = 'index' # General information about the project. -project = "Stegano" -copyright = "2010-2025, Cédric Bonhomme" -author = "Cédric Bonhomme " +project = u'Stegano' +copyright = u'2010-2021, Cédric Bonhomme' +author = 'Cédric Bonhomme ' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = "0.11" +version = '0.9' # The full version, including alpha/beta/rc tags. -release = "0.11.0" +release = '0.9.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None +#language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -# today = '' +#today = '' # Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +#today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ["_build"] +exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None +#default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True +#add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -# add_module_names = True +#add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -# show_authors = False +#show_authors = False # A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] +#modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "sphinx_rtd_theme" +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +#html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +#html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -# html_title = None +#html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None +#html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = None +#html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -# html_favicon = None +#html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' +#html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -# html_use_smartypants = True +#html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -# html_sidebars = {} +#html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -# html_additional_pages = {} +#html_additional_pages = {} # If false, no module index is generated. -# html_domain_indices = True +#html_domain_indices = True # If true, the index is split into individual pages for each letter. -# html_split_index = False +#html_split_index = False # If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True +#html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True +#html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True +#html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -# html_use_opensearch = '' +#html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None +#html_file_suffix = None # -- Options for LaTeX output -------------------------------------------------- -latex_engine = "pdflatex" +latex_engine = 'pdflatex' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "Stgan.tex", "Stegano Documentation", "Cédric Bonhomme", "howto"), + ('index', 'Stgan.tex', u'Stegano Documentation', + u'Cédric Bonhomme', 'howto'), ] latex_show_urls = True latex_show_pagerefs = True -ADDITIONAL_PREAMBLE = r""" +ADDITIONAL_PREAMBLE = """ \setcounter{tocdepth}{3} """ diff --git a/docs/index.rst b/docs/index.rst index 5db6c13..f5ed1c2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,10 @@ Presentation ============ +.. image:: https://builds.sr.ht/~cedric/stegano.svg + :target: https://builds.sr.ht/~cedric/stegano + + Stegano_ is a pure Python steganography_ module. Steganography is the art and science of writing hidden messages in such a way @@ -26,7 +30,7 @@ Moreover some methods of steganalysis_ are provided: - steganalysis of LSB encoding in color images; - statistical steganalysis. -You can also use Stegano through a `Web service `_. +You can also use Stegano through this `Web service `_. Not all functionalities of Stegano are covered. Requirements @@ -37,7 +41,7 @@ Requirements - `piexif`_. -Tutorial +Turorial ======== .. toctree:: @@ -49,7 +53,7 @@ Tutorial steganalysis You can have a look at the -`unit tests `_. +`unit tests `_. License @@ -73,7 +77,7 @@ Contact .. _Python: https://www.python.org -.. _Stegano: https://github.com/cedricbonhomme/Stegano +.. _Stegano: https://sr.ht/~cedric/stegano .. _`Pillow`: https://pypi.python.org/pypi/Pillow .. _`piexif`: https://pypi.python.org/pypi/piexif .. _steganography: http://en.wikipedia.org/wiki/Steganography diff --git a/docs/installation.rst b/docs/installation.rst index 5f31735..ae3a371 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -12,4 +12,7 @@ If you want to retrieve the source code (with the unit tests): .. code-block:: bash - $ git clone https://github.com/cedricbonhomme/Stegano + $ git clone https://git.sr.ht/~cedric/stegano + +.. image:: https://builds.sr.ht/~cedric/stegano.svg + :target: https://builds.sr.ht/~cedric/stegano diff --git a/docs/module.rst b/docs/module.rst index 25ef9da..3b2273c 100644 --- a/docs/module.rst +++ b/docs/module.rst @@ -2,14 +2,15 @@ Using Stegano as a Python module ================================ You can find more examples in the -`unit tests directory `_. +`unit tests directory `_. LSB method ---------- .. code-block:: python - Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux + Python 3.5.1 (default, Dec 7 2015, 11:33:57) + [GCC 4.9.2] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from stegano import lsb >>> secret = lsb.hide("./tests/sample-files/Lenna.png", "Hello world!") @@ -26,33 +27,29 @@ Sets are used in order to select the pixels where the message will be hidden. .. code-block:: python - Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux + Python 3.5.1 (default, Dec 7 2015, 11:33:57) + [GCC 4.9.2] on linux Type "help", "copyright", "credits" or "license" for more information. - >>> from stegano import lsb - >>> from stegano.lsb import generators + >>> from stegano import lsbset + >>> from stegano.lsbset import generators # Hide a secret with the Sieve of Eratosthenes >>> secret_message = "Hello World!" - >>> secret_image = lsb.hide("./tests/sample-files/Lenna.png", secret_message, generators.eratosthenes()) + >>> secret_image = lsbset.hide("./tests/sample-files/Lenna.png", + secret_message, + generators.eratosthenes()) >>> secret_image.save("./image.png") # Try to decode with another generator - >>> message = lsb.reveal("./image.png", generators.fibonacci()) + >>> message = lsbset.reveal("./image.png", generators.fibonacci()) Traceback (most recent call last): - File "/Users/flavien/.local/share/virtualenvs/Stegano-sY_cwr69/bin/stegano-lsb", line 6, in - sys.exit(main()) - File "/Users/flavien/Perso/dev/Stegano/bin/lsb.py", line 190, in main - img_encoded = lsb.hide( - File "/Users/flavien/Perso/dev/Stegano/stegano/lsb/lsb.py", line 63, in hide - hider.encode_pixel((col, row)) - File "/Users/flavien/Perso/dev/Stegano/stegano/tools.py", line 165, in encode_pixel - r, g, b, *a = self.encoded_image.getpixel(coordinate) - File "/Users/flavien/.local/share/virtualenvs/Stegano-sY_cwr69/lib/python3.10/site-packages/PIL/Image.py", line 1481, in getpixel - return self.im.getpixel(xy) - IndexError: image index out of range + File "", line 1, in + File "/home/cedric/projects/Stegano/stegano/lsbset/lsbset.py", line 111, in reveal + for color in img_list[generated_number]: + IndexError: list index out of range # Decode with Eratosthenes - >>> message = lsb.reveal("./image.png", generators.eratosthenes()) + >>> message = lsbset.reveal("./image.png", generators.eratosthenes()) >>> message 'Hello World!' @@ -97,12 +94,6 @@ Sets are used in order to select the pixels where the message will be hidden. syracuse Generate the sequence of Syracuse. - shi_tomashi Shi-Tomachi corner generator of the given points - https://docs.opencv.org/4.x/d4/d8c/tutorial_py_shi_tomasi.html - - triangular_numbers Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n. - http://oeis.org/A000217 - Description field of the image @@ -112,7 +103,8 @@ For JPEG and TIFF images. .. code-block:: python - Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux + Python 3.5.1 (default, Dec 7 2015, 11:33:57) + [GCC 4.9.2] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from stegano import exifHeader >>> secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg", diff --git a/docs/software.rst b/docs/software.rst index dfc9b95..3e232f8 100644 --- a/docs/software.rst +++ b/docs/software.rst @@ -12,56 +12,52 @@ Display help .. code-block:: bash $ stegano-lsb --help - usage: stegano-lsb [-h] {hide,reveal,list-generators} ... + usage: stegano-lsb [-h] {hide,reveal} ... positional arguments: - {hide,reveal,list-generators} - sub-command help - hide hide help - reveal reveal help - list-generators list-generators help + {hide,reveal} sub-command help + hide hide help + reveal reveal help - options: - -h, --help show this help message and exit + optional arguments: + -h, --help show this help message and exit .. code-block:: bash $ stegano-lsb hide --help - usage: stegano-lsb hide [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] (-m SECRET_MESSAGE | -f SECRET_FILE) -o OUTPUT_IMAGE_FILE + usage: stegano-lsb hide [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] + (-m SECRET_MESSAGE | -f SECRET_FILE) -o + OUTPUT_IMAGE_FILE - options: - -h, --help show this help message and exit - -i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE + optional arguments: + -h, --help show this help message and exit + -i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE Input image file. - -e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE} - Specify the encoding of the message to hide. UTF-8 (default) or UTF-32LE. - -g [GENERATOR_FUNCTION ...], --generator [GENERATOR_FUNCTION ...] - Generator (with optional arguments) - -s SHIFT, --shift SHIFT - Shift for the generator - -m SECRET_MESSAGE Your secret message to hide (non binary). - -f SECRET_FILE Your secret to hide (Text or any binary file). - -o OUTPUT_IMAGE_FILE, --output OUTPUT_IMAGE_FILE + -e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE} + Specify the encoding of the message to hide. UTF-8 + (default) or UTF-32LE. + -m SECRET_MESSAGE Your secret message to hide (non binary). + -f SECRET_FILE Your secret to hide (Text or any binary file). + -o OUTPUT_IMAGE_FILE, --output OUTPUT_IMAGE_FILE Output image containing the secret. .. code-block:: bash $ stegano-lsb reveal --help - usage: stegano-lsb reveal [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] [-o SECRET_BINARY] + usage: stegano-lsb reveal [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] + [-o SECRET_BINARY] - options: - -h, --help show this help message and exit - -i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE + optional arguments: + -h, --help show this help message and exit + -i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE Input image file. - -e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE} - Specify the encoding of the message to reveal. UTF-8 (default) or UTF-32LE. - -g [GENERATOR_FUNCTION ...], --generator [GENERATOR_FUNCTION ...] - Generator (with optional arguments) - -s SHIFT, --shift SHIFT - Shift for the generator - -o SECRET_BINARY Output for the binary secret (Text or any binary file). + -e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE} + Specify the encoding of the message to reveal. UTF-8 + (default) or UTF-32LE. + -o SECRET_BINARY Output for the binary secret (Text or any binary + file). Hide and reveal a text message @@ -96,40 +92,63 @@ Hide and reveal a binary file + + + +The command ``stegano-lsb-set`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Sets are used in order to select the pixels where the message will be hidden. -Hide and reveal a text message with set ---------------------------------------- +Hide and reveal a text message +------------------------------ .. code-block:: bash # Hide the message with the Sieve of Eratosthenes - $ stegano-lsb hide -i ./tests/sample-files/Montenach.png --generator eratosthenes -m 'Joyeux Noël!' -o ./surprise.png + $ stegano-lsb-set hide -i ./tests/sample-files/Montenach.png --generator eratosthenes -m 'Joyeux Noël!' -o ./surprise.png # Try to reveal with Mersenne numbers - $ stegano-lsb reveal --generator mersenne -i ./surprise.png + $ stegano-lsb-set reveal --generator mersenne -i ./surprise.png # Try to reveal with fermat numbers - $ stegano-lsb reveal --generator fermat -i ./surprise.png + $ stegano-lsb-set reveal --generator fermat -i ./surprise.png # Try to reveal with carmichael numbers - $ stegano-lsb reveal --generator carmichael -i ./surprise.png + $ stegano-lsb-set reveal --generator carmichael -i ./surprise.png # Try to reveal with Sieve of Eratosthenes - $ stegano-lsb reveal --generator eratosthenes -i ./surprise.png + $ stegano-lsb-set reveal --generator eratosthenes -i ./surprise.png + +An other example: + +.. code-block:: bash + + # Hide the message - LSB with a set defined by the identity function (f(x) = x). + stegano-lsb-set hide -i ./tests/sample-files/Montenach.png --generator identity -m 'I like steganography.' -o ./enc-identity.png + + # Hide the message - LSB only. + stegano-lsb hide -i ./tests/sample-files/Montenach.png -m 'I like steganography.' -o ./enc.png + + # Check if the two generated files are the same. + sha1sum ./enc-identity.png ./enc.png + + # The output of lsb is given to lsb-set. + stegano-lsb-set reveal -i ./enc.png --generator identity + + # The output of lsb-set is given to lsb. + stegano-lsb reveal -i ./enc-identity.png Sometimes it can be useful to skip the first values of a set. For example if you want to hide several messages or because due to the selected generator (Fibonacci starts with 0, 1, 1, etc.). Or maybe you just want to add more complexity. -In this case, simply use the optional arguments ``--shift`` or ``-s``: +In this case, simply use the optional arguments ``--shift``: .. code-block:: bash - $ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'Shifted secret message' -o ~/Lenna1.png --shift 7 - $ stegano-lsb reveal -i ~/Lenna1.png --shift 7 - Shifted secret message + stegano-lsb-set reveal -i ./tests/sample-files/Lenna.png --generator fibonacci --shift 7 List all available generators @@ -137,7 +156,7 @@ List all available generators .. code-block:: bash - $ stegano-lsb list-generators + $ stegano-lsb-set list-generators Generator id: ackermann Desciption: diff --git a/docs/steganalysis.rst b/docs/steganalysis.rst index 3a8b60f..c1f4c73 100644 --- a/docs/steganalysis.rst +++ b/docs/steganalysis.rst @@ -7,7 +7,7 @@ Parity .. code-block:: bash # Hide the message with Sieve of Eratosthenes - stegano-lsb hide -i ./tests/sample-files/20160505T130442.jpg -o ./surprise.png --generator eratosthenes -m 'Very important message.' + stegano-lsb-set hide -i ./tests/sample-files/20160505T130442.jpg -o ./surprise.png --generator eratosthenes -m 'Very important message.' # Steganalysis of the original photo stegano-steganalysis-parity -i ./tests/sample-files/20160505T130442.jpg -o ./surprise_st_original.png @@ -16,4 +16,4 @@ Parity stegano-steganalysis-parity -i ./surprise.png -o ./surprise_st_secret.png # Reveal with Sieve of Eratosthenes - stegano-lsb reveal -i ./surprise.png --generator eratosthenes + stegano-lsb-set reveal -i ./surprise.png --generator eratosthenes diff --git a/poetry.lock b/poetry.lock index 64a2f3e..bbf80e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,297 +1,130 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. - [[package]] name = "alabaster" -version = "0.7.16" -description = "A light, configurable Sphinx theme" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, - {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, -] +python-versions = "*" [[package]] name = "babel" -version = "2.17.0" +version = "2.9.1" description = "Internationalization utilities" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] +[package.dependencies] +pytz = ">=2015.7" [[package]] name = "certifi" -version = "2025.6.15" +version = "2021.5.30" description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, - {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, -] +python-versions = "*" [[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "colorama" -version = "0.4.6" +version = "0.4.4" description = "Cross-platform colored terminal text." +category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {dev = "sys_platform == \"win32\""} +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +toml = ["toml"] [[package]] name = "crayons" version = "0.4.0" description = "TextUI colors for Python." +category = "main" optional = false python-versions = "*" -groups = ["main"] -files = [ - {file = "crayons-0.4.0-py2.py3-none-any.whl", hash = "sha256:e73ad105c78935d71fe454dd4b85c5c437ba199294e7ffd3341842bc683654b1"}, - {file = "crayons-0.4.0.tar.gz", hash = "sha256:bd33b7547800f2cfbd26b38431f9e64b487a7de74a947b0fafc89b45a601813f"}, -] [package.dependencies] colorama = "*" -[[package]] -name = "distlib" -version = "0.3.9" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, -] - [[package]] name = "docutils" -version = "0.19" +version = "0.16" description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] - -[[package]] -name = "filelock" -version = "3.18.0" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "flake8" -version = "6.1.0" +version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false -python-versions = ">=3.8.1" -groups = ["dev"] -files = [ - {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, - {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, -] +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" -pyflakes = ">=3.1.0,<3.2.0" - -[[package]] -name = "identify" -version = "2.6.12" -description = "File identification library for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, - {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, -] - -[package.extras] -license = ["ukkonen"] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "idna" -version = "3.10" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "imagesize" -version = "1.4.1" +version = "1.2.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] + +[[package]] +name = "importlib-metadata" +version = "4.6.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "jinja2" -version = "3.1.6" +version = "3.0.1" description = "A very fast and expressive template engine." +category = "dev" optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] +python-versions = ">=3.6" [package.dependencies] MarkupSafe = ">=2.0" @@ -301,878 +134,622 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] +python-versions = ">=3.6" [[package]] name = "mccabe" -version = "0.7.0" +version = "0.6.1" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] +python-versions = "*" [[package]] name = "mypy" -version = "1.16.1" +version = "0.750" description = "Optional static typing for Python" +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, - {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, - {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, - {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, - {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, - {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, - {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, - {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, - {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, - {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, - {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, - {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, - {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, - {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, - {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, - {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, - {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, - {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, - {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, - {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, - {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, - {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, - {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, - {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, - {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, - {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, - {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, - {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, - {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, - {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, - {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, - {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, -] +python-versions = ">=3.5" [package.dependencies] -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" +mypy-extensions = ">=0.4.0,<0.5.0" +typed-ast = ">=1.4.0,<1.5.0" +typing-extensions = ">=3.7.4" [package.extras] dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] +python-versions = "*" [[package]] name = "nose2" -version = "0.14.2" -description = "unittest with plugins" +version = "0.9.2" +description = "unittest2 with plugins, the succesor to nose" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "nose2-0.14.2-py3-none-any.whl", hash = "sha256:50cd5f5d025dcf4b6b7c8ada9849ba9a5ffb544fc46f8740da1059502c4b7f92"}, - {file = "nose2-0.14.2.tar.gz", hash = "sha256:29e581efcef01a1aba391e817b121a681e9841b3299c1110bba8b1cd1d4074bf"}, -] - -[package.extras] -coverage-plugin = ["coverage"] -dev = ["Sphinx", "sphinx-issues", "sphinx-rtd-theme"] - -[[package]] -name = "numpy" -version = "2.2.6" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "python_version == \"3.10\"" -files = [ - {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, - {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, - {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, - {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, - {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, - {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, - {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, - {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, - {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, - {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, - {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, - {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, -] - -[[package]] -name = "numpy" -version = "2.3.1" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -groups = ["main"] -markers = "python_version >= \"3.11\"" -files = [ - {file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"}, - {file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"}, - {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"}, - {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"}, - {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"}, - {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"}, - {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"}, - {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"}, - {file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"}, - {file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"}, - {file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"}, - {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"}, - {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"}, - {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"}, - {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"}, - {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"}, - {file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"}, - {file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"}, - {file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"}, - {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"}, - {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"}, - {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"}, - {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"}, - {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"}, - {file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"}, - {file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"}, - {file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"}, - {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"}, - {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"}, - {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"}, - {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"}, - {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"}, - {file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"}, - {file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"}, - {file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"}, - {file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"}, - {file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"}, -] - -[[package]] -name = "opencv-python" -version = "4.11.0.86" -description = "Wrapper package for OpenCV python bindings." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4"}, - {file = "opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a"}, - {file = "opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66"}, - {file = "opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202"}, - {file = "opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d"}, - {file = "opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b"}, - {file = "opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec"}, -] +python-versions = "*" [package.dependencies] -numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version == \"3.11\""}, - {version = ">=1.21.4", markers = "python_version == \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version == \"3.10\""}, -] +coverage = ">=4.4.1" +six = ">=1.7" + +[package.extras] +coverage_plugin = ["coverage (>=4.4.1)"] +doc = ["Sphinx (>=1.6.5)", "sphinx-rtd-theme", "mock"] [[package]] name = "packaging" -version = "25.0" +version = "20.9" description = "Core utilities for Python packages" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] +[package.dependencies] +pyparsing = ">=2.0.2" [[package]] name = "piexif" version = "1.1.3" description = "To simplify exif manipulations with python. Writing, reading, and more..." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] -files = [ - {file = "piexif-1.1.3-py2.py3-none-any.whl", hash = "sha256:3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6"}, - {file = "piexif-1.1.3.zip", hash = "sha256:83cb35c606bf3a1ea1a8f0a25cb42cf17e24353fd82e87ae3884e74a302a5f1b"}, -] [[package]] name = "pillow" -version = "11.3.0" +version = "8.3.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, - {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, - {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, - {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, - {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, - {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, - {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, - {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, - {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, - {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, - {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, - {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, - {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, - {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, - {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, - {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, - {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, - {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, - {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, - {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, - {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, - {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, - {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, - {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, - {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, - {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, - {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.3.8" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" +python-versions = ">=3.6" [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.7.0" description = "Python style guide checker" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyflakes" -version = "3.1.0" +version = "2.3.1" description = "passive checker of Python programs" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, - {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.19.2" +version = "2.9.0" description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] +python-versions = ">=3.5" [[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" [[package]] name = "requests" -version = "2.32.4" +version = "2.25.1" description = "Python HTTP for Humans." +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "snowballstemmer" -version = "3.0.1" -description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." +version = "2.1.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" -groups = ["dev"] -files = [ - {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, - {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, -] +python-versions = "*" [[package]] name = "sphinx" -version = "6.2.1" +version = "3.5.4" description = "Python documentation generator" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, -] +python-versions = ">=3.5" [package.dependencies] alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" -imagesize = ">=1.3" -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.12,<0.17" +imagesize = "*" +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" +requests = ">=2.5.0" +snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-htmlhelp = "*" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinxcontrib-applehelp" -version = "2.0.0" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, - {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, -] +python-versions = ">=3.5" [package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "2.0.0" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, - {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, -] +python-versions = ">=3.5" [package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.1.0" +version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, - {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, -] +python-versions = ">=3.6" [package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["html5lib", "pytest"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] [package.extras] -test = ["flake8", "mypy", "pytest"] +test = ["pytest", "flake8", "mypy"] [[package]] name = "sphinxcontrib-qthelp" -version = "2.0.0" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, - {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, -] +python-versions = ">=3.5" [package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["defusedxml (>=0.7.1)", "pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, - {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] +lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version == \"3.10\"" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] +python-versions = ">=3.5" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" [[package]] name = "typing-extensions" -version = "4.14.0" -description = "Backported and Experimental Type Hints for Python 3.9+" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, - {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, -] +python-versions = "*" [[package]] name = "urllib3" -version = "2.5.0" +version = "1.26.6" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -name = "virtualenv" -version = "20.31.2" -description = "Virtual Python Environment builder" +name = "zipp" +version = "3.4.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, - {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" +python-versions = ">=3.6" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -lock-version = "2.1" -python-versions = ">=3.10,<4.0" -content-hash = "984dbecd1b0506ed6b385bb6f81adf8e6cc8bfc30f270027a7d86d107e83a33f" +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "27372002b56d318965b6b4f58d2313b675f2ad062e6ed50fbc85a27e27b72304" + +[metadata.files] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] +babel = [ + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, +] +certifi = [ + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, + {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, + {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, + {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, + {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, + {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, + {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, + {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, + {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, + {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, + {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, + {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, + {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, + {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, + {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, + {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, + {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, + {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, + {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, + {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, + {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, + {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, + {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, + {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, + {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, + {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, + {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, + {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, + {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, + {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, + {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, + {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, + {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, + {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, + {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, + {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, + {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, +] +crayons = [ + {file = "crayons-0.4.0-py2.py3-none-any.whl", hash = "sha256:e73ad105c78935d71fe454dd4b85c5c437ba199294e7ffd3341842bc683654b1"}, + {file = "crayons-0.4.0.tar.gz", hash = "sha256:bd33b7547800f2cfbd26b38431f9e64b487a7de74a947b0fafc89b45a601813f"}, +] +docutils = [ + {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, + {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +imagesize = [ + {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, + {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, + {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, +] +jinja2 = [ + {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, + {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, +] +markupsafe = [ + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy = [ + {file = "mypy-0.750-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:de9ec8dba773b78c49e7bec9a35c9b6fc5235682ad1fc2105752ae7c22f4b931"}, + {file = "mypy-0.750-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3294821b5840d51a3cd7a2bb63b40fc3f901f6a3cfb3c6046570749c4c7ef279"}, + {file = "mypy-0.750-cp35-cp35m-win_amd64.whl", hash = "sha256:6992133c95a2847d309b4b0c899d7054adc60481df6f6b52bb7dee3d5fd157f7"}, + {file = "mypy-0.750-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:41696a7d912ce16fdc7c141d87e8db5144d4be664a0c699a2b417d393994b0c2"}, + {file = "mypy-0.750-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c87ac7233c629f305602f563db07f5221950fe34fe30af072ac838fa85395f78"}, + {file = "mypy-0.750-cp36-cp36m-win_amd64.whl", hash = "sha256:83fa87f556e60782c0fc3df1b37b7b4a840314ba1ac27f3e1a1e10cb37c89c17"}, + {file = "mypy-0.750-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:30e123b24931f02c5d99307406658ac8f9cd6746f0d45a3dcac2fe5fbdd60939"}, + {file = "mypy-0.750-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:02d9bdd3398b636723ecb6c5cfe9773025a9ab7f34612c1cde5c7f2292e2d768"}, + {file = "mypy-0.750-cp37-cp37m-win_amd64.whl", hash = "sha256:088f758a50af31cf8b42688118077292370c90c89232c783ba7979f39ea16646"}, + {file = "mypy-0.750-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4f42675fa278f3913340bb8c3371d191319704437758d7c4a8440346c293ecb2"}, + {file = "mypy-0.750-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f385a0accf353ca1bca4bbf473b9d83ed18d923fdb809d3a70a385da23e25b6a"}, + {file = "mypy-0.750-cp38-cp38-win_amd64.whl", hash = "sha256:54d205ccce6ed930a8a2ccf48404896d456e8b87812e491cb907a355b1a9c640"}, + {file = "mypy-0.750-py3-none-any.whl", hash = "sha256:28e9fbc96d13397a7ddb7fad7b14f373f91b5cff538e0772e77c270468df083c"}, + {file = "mypy-0.750.tar.gz", hash = "sha256:6ecbd0e8e371333027abca0922b0c2c632a5b4739a0c61ffbd0733391e39144c"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nose2 = [ + {file = "nose2-0.9.2-py2.py3-none-any.whl", hash = "sha256:fd4b84c65ecea869080a23bdb8916716f5363df3b899933991c861ada8aa3f48"}, + {file = "nose2-0.9.2.tar.gz", hash = "sha256:8762f77925bbafcdf38331e0e2ee718756fb75ff74b1f9097cd08731ad59ab5e"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +piexif = [ + {file = "piexif-1.1.3-py2.py3-none-any.whl", hash = "sha256:3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6"}, + {file = "piexif-1.1.3.zip", hash = "sha256:83cb35c606bf3a1ea1a8f0a25cb42cf17e24353fd82e87ae3884e74a302a5f1b"}, +] +pillow = [ + {file = "Pillow-8.3.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:333313bcc53a8a7359e98d5458dfe37bfa301da2fd0e0dc41f585ae0cede9181"}, + {file = "Pillow-8.3.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bccd0d604d814e9494f3bf3f077a23835580ed1743c5175581882e7dd1f178c3"}, + {file = "Pillow-8.3.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7beda44f177ee602aa27e0a297da1657d9572679522c8fb8b336b734653516e"}, + {file = "Pillow-8.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:94db5ea640330de0945b41dc77fb4847b4ab6e87149126c71b36b112e8400898"}, + {file = "Pillow-8.3.0-cp36-cp36m-win32.whl", hash = "sha256:856fcbc3201a6cabf0478daa0c0a1a8a175af7e5173e2084ddb91cc707a09dd1"}, + {file = "Pillow-8.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34ce3d993cb4ca840b1e31165b38cb19c64f64f822a8bc5565bde084baff3bdb"}, + {file = "Pillow-8.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:778a819c2d194e08d39d67ddb15ef0d32eba17bf7d0c2773e97bd221b2613a3e"}, + {file = "Pillow-8.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b42ea77f4e7374a67e1f27aaa9c62627dff681f67890e5b8f0c1e21b1500d9d2"}, + {file = "Pillow-8.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53f6e4b73b3899015ac4aa95d99da0f48ea18a6d7c8db672e8bead3fb9570ef5"}, + {file = "Pillow-8.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fb91deb5121b6dde88599bcb3db3fdad9cf33ff3d4ccc5329ee1fe9655a2f7ff"}, + {file = "Pillow-8.3.0-cp37-cp37m-win32.whl", hash = "sha256:8f65d2a98f198e904dbe89ecb10862d5f0511367d823689039e17c4d011de11e"}, + {file = "Pillow-8.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:25f6564df21d15bcac142b4ed92b6c02e53557539f535f31c1f3bcc985484753"}, + {file = "Pillow-8.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:c2d78c8230bda5fc9f6b1d457c7f8f3432f4fe85bed86f80ba3ed73d59775a88"}, + {file = "Pillow-8.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:950e873ceefbd283cbe7bc5b648b832d1dcf89eeded6726ebec42bc7d67966c0"}, + {file = "Pillow-8.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1037288a22cc8ec9d2918a24ded733a1cc4342fd7f21d15d37e6bbe5fb4a7306"}, + {file = "Pillow-8.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:063d17a02a0170c2f880fbd373b2738b089c6adcbd1f7418667bc9e97524c11b"}, + {file = "Pillow-8.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:561339ed7c324bbcb29b5e4f4705c97df950785394b3ac181f5bf6a08088a672"}, + {file = "Pillow-8.3.0-cp38-cp38-win32.whl", hash = "sha256:331f8321418682386e4f0d0e6369f732053f95abddd2af4e1b1ef74a9537ef37"}, + {file = "Pillow-8.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:eccaefbd646022b5313ca4b0c5f1ae6e0d3a52ef66de64970ecf3f9b2a1be751"}, + {file = "Pillow-8.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:6f7517a220aca8b822e25b08b0df9546701a606a328da5bc057e5f32a3f9b07c"}, + {file = "Pillow-8.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e926d6ffa65d0dddb871b7afe117f17bc045951e66afde60eb0eba923db9e"}, + {file = "Pillow-8.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519b3b24dedc81876d893475bade1b92c4ce7c24b9b82224f0bd8daae682e039"}, + {file = "Pillow-8.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:72858a27dd7bd1c40f91c4f85db3b9f121c8412fd66573121febb00d074d0530"}, + {file = "Pillow-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3251557c53c1ed0c345559afc02d2b0a0aa5788042e161366ed90b27bc322d3d"}, + {file = "Pillow-8.3.0-cp39-cp39-win32.whl", hash = "sha256:ce90aad0a3dc0f13a9ff0ab1f43bcbea436089b83c3fadbe37c6f1733b938bf1"}, + {file = "Pillow-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:490c9236ef4762733b6c2e1f1fcb37793cb9c57d860aa84d6994c990461882e5"}, + {file = "Pillow-8.3.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aef0838f28328523e9e5f2c1852dd96fb85768deb0eb8f908c54dad0f44d2f6f"}, + {file = "Pillow-8.3.0-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:713b762892efa8cd5d8dac24d16ac2d2dbf981963ed1b3297e79755f03f8cbb8"}, + {file = "Pillow-8.3.0-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cec702974f162026bf8de47f6f4b7ce9584a63c50002b38f195ee797165fea77"}, + {file = "Pillow-8.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ef8119ce44f90d2f8ac7c58f7da480ada5151f217dc8da03681b73fc91dec3"}, + {file = "Pillow-8.3.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc25d59ecf23ea19571065306806a29c43c67f830f0e8a121303916ba257f484"}, + {file = "Pillow-8.3.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:28f184c0a65be098af412f78b0b6f3bbafd1614e1dc896e810d8357342a794b7"}, + {file = "Pillow-8.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:c3529fb98a40f89269175442c5ff4ef81d22e91b2bdcbd33833a350709b5130c"}, + {file = "Pillow-8.3.0.tar.gz", hash = "sha256:803606e206f3e366eea46b1e7ab4dac74cfac770d04de9c35319814e11e47c46"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pygments = [ + {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, + {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytz = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, + {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, +] +sphinx = [ + {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"}, + {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] +typed-ast = [ + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, +] +typing-extensions = [ + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, +] +urllib3 = [ + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, +] +zipp = [ + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, +] diff --git a/pyproject.toml b/pyproject.toml index 8c0101f..fcedf52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,98 +1,60 @@ -[build-system] -requires = ["poetry-core>=2.0"] -build-backend = "poetry.core.masonry.api" - - -[project] +[tool.poetry] name = "stegano" -version = "2.0.0" +version = "0.9.9" description = "A pure Python Steganography module." authors = [ - {name = "Cédric Bonhomme", email= "cedric@cedricbonhomme.org"} + "Cédric Bonhomme " ] license = "GPL-3.0-or-later" + readme = "README.md" -keywords = ["Steganography", "Security", "Stegano"] -dynamic = ["classifiers"] +homepage = "https://sr.ht/~cedric/stegano" +repository = "https://git.sr.ht/~cedric/stegano" +documentation = "https://stegano.readthedocs.io" -requires-python = ">=3.10,<4.0" -dependencies = [ - "pillow (>=9.5,<12.0)", - "piexif (>=1.1.3)", - "crayons (>=0.4.0)", - "opencv-python (>=4.11.0.86)" -] +keywords = ["steganography", "security", "stegano"] -[project.urls] -Homepage = "https://github.com/cedricbonhomme/Stegano" -Changelog = "https://github.com/cedricbonhomme/Stegano/blob/master/CHANGELOG.md" -Repository = "https://github.com/cedricbonhomme/Stegano" -Documentation = "https://stegano.readthedocs.io" - -[project.scripts] -stegano-lsb = "stegano.console.lsb:main" -stegano-red = "stegano.console.red:main" -stegano-steganalysis-parity = "stegano.console.parity:main" -stegano-steganalysis-statistics = "stegano.console.statistics:main" - - -[tool.poetry] -requires-poetry = ">=2.0" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Topic :: Security", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)" ] + include = [ "README.md", "COPYING", "CHANGELOG.md", "docs/**/*", + "bin/*" ] +[tool.poetry.scripts] +stegano-lsb = "bin.lsb:main" +stegano-lsb-set = "bin.lsbset:main" +stegano-red = "bin.red:main" +stegano-steganalysis-parity = "bin.parity:main" +stegano-steganalysis-statistics = "bin.statistics:main" -[tool.poetry.group.dev.dependencies] -mypy = "^1.8.0" -flake8 = "^6.0.0" -nose2 = "^0.14.0" -Sphinx = "^6.2.1" -pre-commit = "^3.6.0" +[tool.poetry.dependencies] +python = "^3.7" +pillow = "^8.2.0" +piexif = "^1.1.3" +crayons = "^0.4.0" +[tool.poetry.dev-dependencies] +mypy = "^0.750" +flake8 = "^3.7.9" +nose2 = "^0.9.1" +Sphinx = "^3.5.3" -[tool.poetry.group.dev] -optional = true - - -[tool.mypy] -python_version = "3.13" -check_untyped_defs = true -ignore_errors = false -ignore_missing_imports = true -strict_optional = true -no_implicit_optional = true -warn_unused_ignores = true -warn_redundant_casts = true -warn_unused_configs = true -warn_unreachable = true - -show_error_context = true -pretty = true - -exclude = "build|dist|docs" - - -[tool.isort] -profile = "black" - - -[tool.flake8] -ignore = ["E203"] +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/docs/requirements.txt b/requirements-doc.txt similarity index 100% rename from docs/requirements.txt rename to requirements-doc.txt diff --git a/stegano/__init__.py b/stegano/__init__.py index 9c49dc3..c76943b 100755 --- a/stegano/__init__.py +++ b/stegano/__init__.py @@ -1,5 +1,9 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -from . import exifHeader, lsb, red, steganalysis +from . import red +from . import exifHeader +from . import lsb +from . import lsbset -__all__ = ["red", "exifHeader", "lsb", "steganalysis"] +from . import steganalysis diff --git a/stegano/console/__init__.py b/stegano/console/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stegano/console/lsb.py b/stegano/console/lsb.py deleted file mode 100755 index 7ab98ca..0000000 --- a/stegano/console/lsb.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python -# Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : https://github.com/cedricbonhomme/Stegano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.7 $" -__date__ = "$Date: 2016/03/18 $" -__revision__ = "$Date: 2019/06/04 $" -__license__ = "GPLv3" - -import inspect - -import crayons - -try: - from stegano import lsb - from stegano.lsb import generators -except Exception: - print("Install stegano: pipx install Stegano") - -import argparse - -from stegano import tools - - -class ValidateGenerator(argparse.Action): - def __call__(self, parser, args, values, option_string=None): - valid_generators = [ - generator[0] - for generator in inspect.getmembers(generators, inspect.isfunction) - ] - # Verify that the generator is valid - generator = values[0] - if generator not in valid_generators: - raise ValueError("Unknown generator: %s" % generator) - # Set the generator_function arg of the parser - setattr(args, self.dest, values) - - -def main(): - parser = argparse.ArgumentParser(prog="stegano-lsb") - subparsers = parser.add_subparsers( - help="sub-command help", dest="command", required=True - ) - - # Subparser: Hide - parser_hide = subparsers.add_parser("hide", help="hide help") - # Original image - parser_hide.add_argument( - "-i", - "--input", - dest="input_image_file", - required=True, - help="Input image file.", - ) - parser_hide.add_argument( - "-e", - "--encoding", - dest="encoding", - choices=tools.ENCODINGS.keys(), - default="UTF-8", - help="Specify the encoding of the message to hide." - " UTF-8 (default) or UTF-32LE.", - ) - - # Generator - parser_hide.add_argument( - "-g", - "--generator", - dest="generator_function", - action=ValidateGenerator, - nargs="*", - required=False, - default=None, - help="Generator (with optional arguments)", - ) - - # Shift the message to hide - parser_hide.add_argument( - "-s", "--shift", dest="shift", default=0, help="Shift for the generator" - ) - - group_secret = parser_hide.add_mutually_exclusive_group(required=True) - # Non binary secret message to hide - group_secret.add_argument( - "-m", dest="secret_message", help="Your secret message to hide (non binary)." - ) - # Binary secret message to hide - group_secret.add_argument( - "-f", dest="secret_file", help="Your secret to hide (Text or any binary file)." - ) - - # Image containing the secret - parser_hide.add_argument( - "-o", - "--output", - dest="output_image_file", - required=True, - help="Output image containing the secret.", - ) - - # Subparser: Reveal - parser_reveal = subparsers.add_parser("reveal", help="reveal help") - parser_reveal.add_argument( - "-i", - "--input", - dest="input_image_file", - required=True, - help="Input image file.", - ) - parser_reveal.add_argument( - "-e", - "--encoding", - dest="encoding", - choices=tools.ENCODINGS.keys(), - default="UTF-8", - help="Specify the encoding of the message to reveal." - " UTF-8 (default) or UTF-32LE.", - ) - - # Generator - parser_reveal.add_argument( - "-g", - "--generator", - dest="generator_function", - action=ValidateGenerator, - nargs="*", - required=False, - help="Generator (with optional arguments)", - ) - - # Shift the message to reveal - parser_reveal.add_argument( - "-s", "--shift", dest="shift", default=0, help="Shift for the generator" - ) - parser_reveal.add_argument( - "-o", - dest="secret_binary", - help="Output for the binary secret (Text or any binary file).", - ) - - # Subparser: List generators - subparsers.add_parser("list-generators", help="list-generators help") - - arguments = parser.parse_args() - - if arguments.command != "list-generators": - if not arguments.generator_function: - generator = None - else: - try: - if arguments.generator_function[0] == "LFSR": - # Compute the size of the image for use by the LFSR generator if needed - tmp = tools.open_image(arguments.input_image_file) - size = tmp.width * tmp.height - tmp.close() - arguments.generator_function.append(size) - if len(arguments.generator_function) > 1: - generator = getattr(generators, arguments.generator_function[0])( - *[int(e) for e in arguments.generator_function[1:]] - ) - else: - generator = getattr(generators, arguments.generator_function[0])() - - except AttributeError: - print(f"Unknown generator: {arguments.generator_function}") - exit(1) - - if arguments.command == "hide": - if arguments.secret_message is not None: - secret = arguments.secret_message - elif arguments.secret_file != "": - secret = tools.binary2base64(arguments.secret_file) - - img_encoded = lsb.hide( - image=arguments.input_image_file, - message=secret, - generator=generator, - shift=int(arguments.shift), - encoding=arguments.encoding, - ) - try: - img_encoded.save(arguments.output_image_file) - except Exception as e: - # If hide() returns an error (Too long message). - print(e) - - elif arguments.command == "reveal": - try: - secret = lsb.reveal( - encoded_image=arguments.input_image_file, - generator=generator, - shift=int(arguments.shift), - encoding=arguments.encoding, - ) - except IndexError: - print("Impossible to detect message.") - exit(0) - if arguments.secret_binary is not None: - data = tools.base642binary(secret) - with open(arguments.secret_binary, "wb") as f: - f.write(data) - else: - print(secret) - - elif arguments.command == "list-generators": - all_generators = inspect.getmembers(generators, inspect.isfunction) - for generator in all_generators: - print("Generator id:") - print(f" {crayons.green(generator[0], bold=True)}") - print("Desciption:") - print(f" {generator[1].__doc__}") diff --git a/stegano/console/wav.py b/stegano/console/wav.py deleted file mode 100644 index d32d322..0000000 --- a/stegano/console/wav.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python -# Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : https://github.com/cedricbonhomme/Stegano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.7 $" -__date__ = "$Date: 2016/03/18 $" -__revision__ = "$Date: 2019/06/04 $" -__license__ = "GPLv3" - -try: - from stegano import wav -except Exception: - print("Install stegano: pipx install Stegano") - -import argparse - -from stegano import tools - - -def main(): - parser = argparse.ArgumentParser(prog="stegano-lsb") - subparsers = parser.add_subparsers( - help="sub-command help", dest="command", required=True - ) - - # Subparser: Hide - parser_hide = subparsers.add_parser("hide", help="hide help") - # Original audio - parser_hide.add_argument( - "-i", - "--input", - dest="input_audio_file", - required=True, - help="Input audio file.", - ) - parser_hide.add_argument( - "-e", - "--encoding", - dest="encoding", - choices=tools.ENCODINGS.keys(), - default="UTF-8", - help="Specify the encoding of the message to hide." - " UTF-8 (default) or UTF-32LE.", - ) - - group_secret = parser_hide.add_mutually_exclusive_group(required=True) - # Non binary secret message to hide - group_secret.add_argument( - "-m", dest="secret_message", help="Your secret message to hide (non binary)." - ) - # Binary secret message to hide - group_secret.add_argument( - "-f", dest="secret_file", help="Your secret to hide (Text or any binary file)." - ) - - # Audio containing the secret - parser_hide.add_argument( - "-o", - "--output", - dest="output_audio_file", - required=True, - help="Output audio containing the secret.", - ) - - # Subparser: Reveal - parser_reveal = subparsers.add_parser("reveal", help="reveal help") - parser_reveal.add_argument( - "-i", - "--input", - dest="input_audio_file", - required=True, - help="Input audio file.", - ) - parser_reveal.add_argument( - "-e", - "--encoding", - dest="encoding", - choices=tools.ENCODINGS.keys(), - default="UTF-8", - help="Specify the encoding of the message to reveal." - " UTF-8 (default) or UTF-32LE.", - ) - - arguments = parser.parse_args() - - if arguments.command == "hide": - if arguments.secret_message is not None: - secret = arguments.secret_message - elif arguments.secret_file != "": - secret = tools.binary2base64(arguments.secret_file) - - wav.hide( - input_file=arguments.input_audio_file, - message=secret, - encoding=arguments.encoding, - output_file=arguments.output_audio_file, - ) - - elif arguments.command == "reveal": - try: - secret = wav.reveal( - input_file=arguments.input_audio_file, encoding=arguments.encoding - ) - except IndexError: - print("Impossible to detect message.") - exit(0) - if arguments.secret_binary is not None: - data = tools.base642binary(secret) - with open(arguments.secret_binary, "wb") as f: - f.write(data) - else: - print(secret) diff --git a/stegano/exifHeader/__init__.py b/stegano/exifHeader/__init__.py index 6fcb15f..a64a46c 100644 --- a/stegano/exifHeader/__init__.py +++ b/stegano/exifHeader/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -from .exifHeader import hide, reveal - -__all__ = ["hide", "reveal"] +from .exifHeader import * diff --git a/stegano/exifHeader/exifHeader.py b/stegano/exifHeader/exifHeader.py index 1fefa93..8918331 100644 --- a/stegano/exifHeader/exifHeader.py +++ b/stegano/exifHeader/exifHeader.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,28 +26,24 @@ __revision__ = "$Date: 2017/01/18 $" __license__ = "GPLv3" import piexif - from stegano import tools def hide( - input_image_file, - img_enc, - secret_message=None, - secret_file=None, - img_format=None, + input_image_file, img_enc, secret_message=None, secret_file=None, img_format=None, ): - """Hide a message (string) in an image.""" - from base64 import b64encode + """Hide a message (string) in an image. + """ from zlib import compress + from base64 import b64encode - if secret_file is not None: - with open(secret_file, "rb") as f: + if secret_file != None: + with open(secret_file, "r") as f: secret_message = f.read() try: text = compress(b64encode(bytes(secret_message, "utf-8"))) - except Exception: + except: text = compress(b64encode(secret_message)) img = tools.open_image(input_image_file) @@ -66,7 +64,8 @@ def hide( def reveal(input_image_file): - """Find a message in an image.""" + """Find a message in an image. + """ from base64 import b64decode from zlib import decompress diff --git a/stegano/lsb/__init__.py b/stegano/lsb/__init__.py index 81b5525..1ffc44a 100644 --- a/stegano/lsb/__init__.py +++ b/stegano/lsb/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -from .lsb import hide, reveal - -__all__ = ["hide", "reveal"] +from .lsb import * diff --git a/stegano/lsb/lsb.py b/stegano/lsb/lsb.py old mode 100644 new mode 100755 index 918d939..4729257 --- a/stegano/lsb/lsb.py +++ b/stegano/lsb/lsb.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,73 +20,112 @@ # along with this program. If not, see __author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.7 $" -__date__ = "$Date: 2016/03/13 $" -__revision__ = "$Date: 2019/05/31 $" +__version__ = "$Revision: 0.4 $" +__date__ = "$Date: 2016/08/04 $" +__revision__ = "$Date: 2019/06/01 $" __license__ = "GPLv3" -from typing import IO, Iterator, Union +from typing import IO, Union from stegano import tools -from .generators import identity - def hide( - image: Union[str, IO[bytes]], + input_image: Union[str, IO[bytes]], message: str, - generator: Union[None, Iterator[int]] = None, - shift: int = 0, encoding: str = "UTF-8", + shift: int = 0, auto_convert_rgb: bool = False, ): """Hide a message (string) in an image with the LSB (Least Significant Bit) technique. """ - hider = tools.Hider(image, message, encoding, auto_convert_rgb) - width = hider.encoded_image.width + message_length = len(message) + assert message_length != 0, "message length is zero" - if not generator: - generator = identity() + img = tools.open_image(input_image) - while shift != 0: - next(generator) - shift -= 1 + if img.mode not in ["RGB", "RGBA"]: + if not auto_convert_rgb: + print("The mode of the image is not RGB. Mode is {}".format(img.mode)) + answer = input("Convert the image to RGB ? [Y / n]\n") or "Y" + if answer.lower() == "n": + raise Exception("Not a RGB image.") + img = img.convert("RGB") - while hider.encode_another_pixel(): - generated_number = next(generator) + encoded = img.copy() + width, height = img.size + index = 0 - col = generated_number % width - row = int(generated_number / width) + message = str(message_length) + ":" + str(message) + message_bits = "".join(tools.a2bits_list(message, encoding)) + message_bits += "0" * ((3 - (len(message_bits) % 3)) % 3) - hider.encode_pixel((col, row)) + npixels = width * height + len_message_bits = len(message_bits) + if len_message_bits > npixels * 3: + raise Exception( + "The message you want to hide is too long: {}".format(message_length) + ) + for row in range(height): + for col in range(width): + if shift != 0: + shift -= 1 + continue + if index + 3 <= len_message_bits: - return hider.encoded_image + # Get the colour component. + pixel = img.getpixel((col, row)) + r = pixel[0] + g = pixel[1] + b = pixel[2] + + # Change the Least Significant Bit of each colour component. + r = tools.setlsb(r, message_bits[index]) + g = tools.setlsb(g, message_bits[index + 1]) + b = tools.setlsb(b, message_bits[index + 2]) + + # Save the new pixel + if img.mode == "RGBA": + encoded.putpixel((col, row), (r, g, b, pixel[3])) + else: + encoded.putpixel((col, row), (r, g, b)) + + index += 3 + else: + img.close() + return encoded -def reveal( - encoded_image: Union[str, IO[bytes]], - generator: Union[None, Iterator[int]] = None, - shift: int = 0, - encoding: str = "UTF-8", - close_file: bool = True, -): - """Find a message in an image (with the LSB technique).""" - revealer = tools.Revealer(encoded_image, encoding, close_file) - width = revealer.encoded_image.width +def reveal(input_image: Union[str, IO[bytes]], encoding: str = "UTF-8", shift: int = 0): + """Find a message in an image (with the LSB technique). + """ + img = tools.open_image(input_image) + width, height = img.size + buff, count = 0, 0 + bitab = [] + limit = None + for row in range(height): + for col in range(width): + if shift != 0: + shift -= 1 + continue + # pixel = [r, g, b] or [r,g,b,a] + pixel = img.getpixel((col, row)) + if img.mode == "RGBA": + pixel = pixel[:3] # ignore the alpha + for color in pixel: + buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count) + count += 1 + if count == tools.ENCODINGS[encoding]: + bitab.append(chr(buff)) + buff, count = 0, 0 + if bitab[-1] == ":" and limit is None: + try: + limit = int("".join(bitab[:-1])) + except Exception: + pass - if not generator: - generator = identity() - - while shift != 0: - next(generator) - shift -= 1 - - while True: - generated_number = next(generator) - - col = generated_number % width - row = int(generated_number / width) - - if revealer.decode_pixel((col, row)): - return revealer.secret_message + if len(bitab) - len(str(limit)) - 1 == limit: + img.close() + return "".join(bitab)[len(str(limit)) + 1:] diff --git a/stegano/lsbset/__init__.py b/stegano/lsbset/__init__.py new file mode 100644 index 0000000..22954f0 --- /dev/null +++ b/stegano/lsbset/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .lsbset import * diff --git a/stegano/lsb/generators.py b/stegano/lsbset/generators.py similarity index 76% rename from stegano/lsb/generators.py rename to stegano/lsbset/generators.py index 3780fc9..e95b74d 100644 --- a/stegano/lsb/generators.py +++ b/stegano/lsbset/generators.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,19 +22,17 @@ __author__ = "Cedric Bonhomme" __version__ = "$Revision: 0.3 $" __date__ = "$Date: 2011/12/28 $" -__revision__ = "$Date: 2021/11/29 $" +__revision__ = "$Date: 2019/06/04 $" __license__ = "GPLv3" import itertools import math -from typing import Any, Dict, Iterator, List - -import cv2 -import numpy as np +from typing import Dict, Iterator, List def identity() -> Iterator[int]: - """f(x) = x""" + """f(x) = x + """ n = 0 while True: yield n @@ -72,7 +72,7 @@ def eratosthenes() -> Iterator[int]: """Generate the prime numbers with the sieve of Eratosthenes. https://oeis.org/A000040 """ - d: Dict[int, List[int]] = {} + d = {} # type: Dict[int, List[int]] for i in itertools.count(2): if i in d: for j in d[i]: @@ -89,7 +89,8 @@ def composite() -> Iterator[int]: """ p1 = 3 for p2 in eratosthenes(): - yield from range(p1 + 1, p2) + for n in range(p1 + 1, p2): + yield n p1 = p2 @@ -107,7 +108,8 @@ def carmichael() -> Iterator[int]: def ackermann_slow(m: int, n: int) -> int: - """Ackermann number.""" + """Ackermann number. + """ if m == 0: return n + 1 elif n == 0: @@ -117,7 +119,8 @@ def ackermann_slow(m: int, n: int) -> int: def ackermann_naive(m: int) -> Iterator[int]: - """Naive Ackermann encapsulated in a generator.""" + """Naive Ackermann encapsulated in a generator + """ n = 0 while True: yield ackermann_slow(m, n) @@ -125,7 +128,8 @@ def ackermann_naive(m: int) -> Iterator[int]: def ackermann_fast(m: int, n: int) -> int: - """Ackermann number.""" + """Ackermann number. + """ while m >= 4: if n == 0: n = 1 @@ -143,7 +147,8 @@ def ackermann_fast(m: int, n: int) -> int: def ackermann(m: int) -> Iterator[int]: - """Ackermann encapsulated in a generator.""" + """Ackermann encapsulated in a generator. + """ n = 0 while True: yield ackermann_fast(m, n) @@ -161,7 +166,8 @@ def fibonacci() -> Iterator[int]: def log_gen() -> Iterator[int]: - """Logarithmic generator.""" + """Logarithmic generator. + """ y = 1 while True: adder = max(1, math.pow(10, int(math.log10(y)))) @@ -223,32 +229,5 @@ def LFSR(m: int) -> Iterator[int]: # Add the feedback bit state.insert(0, feedback) # Convert the registers to an int - out = sum(e * (2**i) for i, e in enumerate(state)) + out = sum([e * (2 ** i) for i, e in enumerate(state)]) yield out - - -def shi_tomashi( - image_path: str, - max_corners: int = 100, - quality: float = 0.01, - min_distance: float = 10.0, -) -> Iterator[int]: - """Shi-Tomachi corner generator of the given points - https://docs.opencv.org/4.x/d4/d8c/tutorial_py_shi_tomasi.html - """ - image = cv2.imread(image_path) - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - corners: np.ndarray = cv2.goodFeaturesToTrack( - gray, max_corners, quality, min_distance - ) - corners_int: np.ndarray[Any, np.dtype[np.signedinteger[Any]]] = np.array( - np.intp(corners) - ) - i = 0 - while True: - x, y = corners_int[i].ravel() - # Compute the pixel number with top left of image as origin - # using coordinates of the corner. - # (y * number of pixels a row) + pixels left in last row - yield (y * image.shape[1]) + x - i += 1 diff --git a/stegano/lsbset/lsbset.py b/stegano/lsbset/lsbset.py new file mode 100644 index 0000000..3a9583e --- /dev/null +++ b/stegano/lsbset/lsbset.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Stegano - Stegano is a pure Python steganography module. +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org +# +# For more information : https://git.sr.ht/~cedric/stegano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +__author__ = "Cedric Bonhomme" +__version__ = "$Revision: 0.7 $" +__date__ = "$Date: 2016/03/13 $" +__revision__ = "$Date: 2019/05/31 $" +__license__ = "GPLv3" + +from typing import IO, Iterator, Union + +from PIL import Image + +from stegano import tools + + +def hide( + input_image: Union[str, IO[bytes]], + message: str, + generator: Iterator[int], + shift: int = 0, + encoding: str = "UTF-8", + auto_convert_rgb: bool = False, +): + """Hide a message (string) in an image with the + LSB (Least Significant Bit) technique. + """ + message_length = len(message) + assert message_length != 0, "message length is zero" + + img = tools.open_image(input_image) + + if img.mode not in ["RGB", "RGBA"]: + if not auto_convert_rgb: + print("The mode of the image is not RGB. Mode is {}".format(img.mode)) + answer = input("Convert the image to RGB ? [Y / n]\n") or "Y" + if answer.lower() == "n": + raise Exception("Not a RGB image.") + img = img.convert("RGB") + + img_list = list(img.getdata()) + width, height = img.size + index = 0 + + message = str(message_length) + ":" + str(message) + message_bits = "".join(tools.a2bits_list(message, encoding)) + message_bits += "0" * ((3 - (len(message_bits) % 3)) % 3) + + npixels = width * height + len_message_bits = len(message_bits) + if len_message_bits > npixels * 3: + raise Exception( + "The message you want to hide is too long: {}".format(message_length) + ) + while shift != 0: + next(generator) + shift -= 1 + + while index + 3 <= len_message_bits: + generated_number = next(generator) + r, g, b, *a = img_list[generated_number] + + # Change the Least Significant Bit of each colour component. + r = tools.setlsb(r, message_bits[index]) + g = tools.setlsb(g, message_bits[index + 1]) + b = tools.setlsb(b, message_bits[index + 2]) + + # Save the new pixel + if img.mode == "RGBA": + img_list[generated_number] = (r, g, b, *a) + else: + img_list[generated_number] = (r, g, b) + + index += 3 + + # create empty new image of appropriate format + encoded = Image.new(img.mode, (img.size)) + + # insert saved data into the image + encoded.putdata(img_list) + + return encoded + + +def reveal( + input_image: Union[str, IO[bytes]], + generator: Iterator[int], + shift: int = 0, + encoding: str = "UTF-8", +): + """Find a message in an image (with the LSB technique). + """ + img = tools.open_image(input_image) + img_list = list(img.getdata()) + width, height = img.size + buff, count = 0, 0 + bitab = [] + limit = None + + while shift != 0: + next(generator) + shift -= 1 + + while True: + generated_number = next(generator) + # color = [r, g, b] + for color in img_list[generated_number][:3]: # ignore the alpha + buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count) + count += 1 + if count == tools.ENCODINGS[encoding]: + bitab.append(chr(buff)) + buff, count = 0, 0 + if bitab[-1] == ":" and limit == None: + if "".join(bitab[:-1]).isdigit(): + limit = int("".join(bitab[:-1])) + else: + raise IndexError("Impossible to detect message.") + if len(bitab) - len(str(limit)) - 1 == limit: + return "".join(bitab)[len(str(limit)) + 1 :] diff --git a/stegano/red/__init__.py b/stegano/red/__init__.py index c599723..d3c8604 100644 --- a/stegano/red/__init__.py +++ b/stegano/red/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- -from .red import hide, reveal - -__all__ = ["hide", "reveal"] +from .red import * diff --git a/stegano/red/red.py b/stegano/red/red.py index e6e2429..4865460 100755 --- a/stegano/red/red.py +++ b/stegano/red/red.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stéganô is a basic Python Steganography module. -# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,7 +25,7 @@ __date__ = "$Date: 2010/10/01 $" __revision__ = "$Date: 2017/02/06 $" __license__ = "GPLv3" -from typing import IO, Union, cast +from typing import IO, Union from stegano import tools @@ -40,17 +42,13 @@ def hide(input_image: Union[str, IO[bytes]], message: str): assert message_length != 0, "message message_length is zero" assert message_length < 255, "message is too long" img = tools.open_image(input_image) - # Ensure image mode is RGB - if img.mode != "RGB": - img = img.convert("RGB") # Use a copy of image to hide the text in encoded = img.copy() width, height = img.size index = 0 for row in range(height): for col in range(width): - pixel = cast(tuple[int, int, int], img.getpixel((col, row))) - r, g, b = pixel + (r, g, b) = img.getpixel((col, row)) # first value is message_length of message if row == 0 and col == 0 and index < message_length: asc = message_length @@ -74,16 +72,12 @@ def reveal(input_image: Union[str, IO[bytes]]): The red value of the first pixel is used for message_length of string. """ img = tools.open_image(input_image) - # Ensure image mode is RGB - if img.mode != "RGB": - img = img.convert("RGB") width, height = img.size message = "" index = 0 for row in range(height): for col in range(width): - pixel = cast(tuple[int, int, int], img.getpixel((col, row))) - r, g, b = pixel + r, g, b = img.getpixel((col, row)) # First pixel r value is length of message if row == 0 and col == 0: message_length = r diff --git a/stegano/steganalysis/__init__.py b/stegano/steganalysis/__init__.py index 4265cc3..239d66c 100644 --- a/stegano/steganalysis/__init__.py +++ b/stegano/steganalysis/__init__.py @@ -1 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + +from .parity import * +from .statistics import * diff --git a/stegano/steganalysis/parity.py b/stegano/steganalysis/parity.py index 36cb42a..193e98d 100644 --- a/stegano/steganalysis/parity.py +++ b/stegano/steganalysis/parity.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,8 +25,6 @@ __date__ = "$Date: 2010/10/01 $" __revision__ = "$Date: 2019/06/06 $" __license__ = "GPLv3" -from typing import cast - from PIL import Image @@ -36,10 +36,7 @@ def steganalyse(img: Image.Image) -> Image.Image: width, height = img.size for row in range(height): for col in range(width): - if pixel := cast(tuple[int, int, int], img.getpixel((col, row))): - r, g, b = pixel[0:3] - else: - raise Exception("Error during steganlysis.") + r, g, b = img.getpixel((col, row))[0:3] if r % 2 == 0: r = 0 else: diff --git a/stegano/steganalysis/statistics.py b/stegano/steganalysis/statistics.py index 52cb296..dde980e 100644 --- a/stegano/steganalysis/statistics.py +++ b/stegano/steganalysis/statistics.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,19 +22,24 @@ __author__ = "Cedric Bonhomme" __version__ = "$Revision: 0.2 $" __date__ = "$Date: 2010/10/01 $" -__revision__ = "$Date: 2021/11/01 $" +__revision__ = "$Date: 2016/08/26 $" __license__ = "GPLv3" import typing -from collections import Counter, OrderedDict +import operator + +from PIL import Image +from collections import Counter +from collections import OrderedDict def steganalyse(img): """ Steganlysis of the LSB technique. """ + encoded = img.copy() width, height = img.size - colours_counter: typing.Counter[int] = Counter() + colours_counter = Counter() # type: typing.Counter[int] for row in range(height): for col in range(width): r, g, b = img.getpixel((col, row)) @@ -43,7 +50,7 @@ def steganalyse(img): sorted(list(colours_counter.items()), key=lambda t: t[1]) ) - colours: float = 0 + colours = 0 # type: float for colour in list(dict_colours.keys()): colours += colour colours = colours / len(dict_colours) diff --git a/stegano/tools.py b/stegano/tools.py index fc018f7..a1cd923 100755 --- a/stegano/tools.py +++ b/stegano/tools.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,7 +28,7 @@ __license__ = "GPLv3" import base64 import itertools from functools import reduce -from typing import IO, List, Union, cast +from typing import IO, Iterator, List, Tuple, Union from PIL import Image @@ -65,16 +67,20 @@ def a2bits_list(chars: str, encoding: str = "UTF-8") -> List[str]: def bs(s: int) -> str: - """Converts an int to its bits representation as a string of 0's and 1's.""" + """Converts an int to its bits representation as a string of 0's and 1's. + """ return str(s) if s <= 1 else bs(s >> 1) + str(s & 1) def setlsb(component: int, bit: str) -> int: - """Set Least Significant Bit of a colour component.""" + """Set Least Significant Bit of a colour component. + """ return component & ~1 | int(bit) -def n_at_a_time(items: List[int], n: int, fillvalue: str): +def n_at_a_time( + items: List[int], n: int, fillvalue: str +) -> Iterator[Tuple[Union[int, str]]]: """Returns an iterator which groups n items at a time. Any final partial tuple will be padded with the fillvalue @@ -96,135 +102,19 @@ def binary2base64(binary_file: str) -> str: def base642binary(b64_fname: str) -> bytes: - """Convert a printable string to a binary file.""" + """Convert a printable string to a binary file. + """ b64_fname += "===" return base64.b64decode(b64_fname) -def open_image(fname_or_instance: Union[str, IO[bytes], Image.Image]) -> Image.Image: - """Opens an image and returns it. +def open_image(fname_or_instance: Union[str, IO[bytes]]): + """Opens a Image and returns it. - :param fname_or_instance: Can be a path to the image (str), - a file-like object (IO[bytes]), - or a PIL Image instance. + :param fname_or_instance: Can either be the location of the image as a + string or the Image.Image instance itself. """ if isinstance(fname_or_instance, Image.Image): return fname_or_instance return Image.open(fname_or_instance) - - -class Hider: - def __init__( - self, - input_image: Union[str, IO[bytes]], - message: str, - encoding: str = "UTF-8", - auto_convert_rgb: bool = False, - ): - self._index = 0 - - message_length = len(message) - assert message_length != 0, "message length is zero" - - image = open_image(input_image) - - if image.mode not in ["RGB", "RGBA"]: - if not auto_convert_rgb: - print(f"The mode of the image is not RGB. Mode is {image.mode}") - answer = input("Convert the image to RGB ? [Y / n]\n") or "Y" - if answer.lower() == "n": - raise Exception("Not a RGB image.") - - image = image.convert("RGB") - - self.encoded_image = image.copy() - image.close() - - message = str(message_length) + ":" + str(message) - self._message_bits = "".join(a2bits_list(message, encoding)) - self._message_bits += "0" * ((3 - (len(self._message_bits) % 3)) % 3) - - width, height = self.encoded_image.size - npixels = width * height - self._len_message_bits = len(self._message_bits) - - if self._len_message_bits > npixels * 3: - raise Exception( - f"The message you want to hide is too long: {message_length}" - ) - - def encode_another_pixel(self): - return True if self._index + 3 <= self._len_message_bits else False - - def encode_pixel(self, coordinate: tuple): - # Determine expected pixel format based on mode - if self.encoded_image.mode == "RGBA": - r, g, b, *a = cast( - tuple[int, int, int, int], self.encoded_image.getpixel(coordinate) - ) - else: - r, g, b, *a = cast( - tuple[int, int, int], self.encoded_image.getpixel(coordinate) - ) - - # Change the Least Significant Bit of each colour component. - r = setlsb(r, self._message_bits[self._index]) - g = setlsb(g, self._message_bits[self._index + 1]) - b = setlsb(b, self._message_bits[self._index + 2]) - - # Save the new pixel - if self.encoded_image.mode == "RGBA": - self.encoded_image.putpixel(coordinate, (r, g, b, *a)) - else: - self.encoded_image.putpixel(coordinate, (r, g, b)) - - self._index += 3 - - -class Revealer: - def __init__( - self, - encoded_image: Union[str, IO[bytes]], - encoding: str = "UTF-8", - close_file: bool = True, - ): - self.encoded_image = open_image(encoded_image) - self._encoding_length = ENCODINGS[encoding] - self._buff, self._count = 0, 0 - self._bitab: List[str] = [] - self._limit: Union[None, int] = None - self.secret_message = "" - self.close_file = close_file - - def decode_pixel(self, coordinate: tuple): - # Tell mypy that this will be a 3- or 4-tuple of ints - pixel = cast( - tuple[int, int, int] | tuple[int, int, int, int], - self.encoded_image.getpixel(coordinate), - ) - - if self.encoded_image.mode == "RGBA": - pixel = pixel[:3] # ignore the alpha - - for color in pixel: - self._buff += (color & 1) << (self._encoding_length - 1 - self._count) - self._count += 1 - - if self._count == self._encoding_length: - self._bitab.append(chr(self._buff)) - self._buff, self._count = 0, 0 - - if self._bitab[-1] == ":" and self._limit is None: - if "".join(self._bitab[:-1]).isdigit(): - self._limit = int("".join(self._bitab[:-1])) - else: - raise IndexError("Impossible to detect message.") - - if len(self._bitab) - len(str(self._limit)) - 1 == self._limit: - self.secret_message = "".join(self._bitab)[len(str(self._limit)) + 1 :] - if self.close_file: - self.encoded_image.close() - return True - else: - return False diff --git a/stegano/wav/__init__.py b/stegano/wav/__init__.py deleted file mode 100644 index e2529fe..0000000 --- a/stegano/wav/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -from .wav import hide, reveal - -__all__ = ["hide", "reveal"] diff --git a/stegano/wav/wav.py b/stegano/wav/wav.py deleted file mode 100644 index 56c5146..0000000 --- a/stegano/wav/wav.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python -# Stegano - Stéganô is a basic Python Steganography module. -# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : https://github.com/cedricbonhomme/Stegano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.2 $" -__date__ = "$Date: 2010/10/01 $" -__revision__ = "$Date: 2017/02/06 $" -__license__ = "GPLv3" - -import wave -from typing import IO, Union - -from stegano import tools - - -def hide( - input_file: Union[str, IO[bytes]], - message: str, - output_file: Union[str, IO[bytes]], - encoding: str = "UTF-8", -): - """ - Hide a message (string) in a .wav audio file. - - Use the lsb of each PCM encoded sample to hide the message string characters as ASCII values. - The first eight bits are used for message_length of the string. - """ - message_length = len(message) - assert message_length != 0, "message message_length is zero" - assert message_length < 255, "message is too long" - - output = wave.open(output_file, "wb") - with wave.open(input_file, "rb") as input: - # get .wav params - nchannels, sampwidth, framerate, nframes, comptype, _ = input.getparams() - assert comptype == "NONE", "only uncompressed files are supported" - - nsamples = nframes * nchannels - - message_bits = f"{message_length:08b}" + "".join( - tools.a2bits_list(message, encoding) - ) - assert len(message_bits) <= nsamples, "message is too long" - - # copy over .wav params to output - output.setnchannels(nchannels) - output.setsampwidth(sampwidth) - output.setframerate(framerate) - - # encode message in frames - frames = bytearray(input.readframes(nsamples)) - for i in range(nsamples): - if i < len(message_bits): - if message_bits[i] == "0": - frames[i] = frames[i] & ~1 - else: - frames[i] = frames[i] | 1 - - # write out - output.writeframes(frames) - - -def reveal(input_file: Union[str, IO[bytes]], encoding: str = "UTF-8"): - """ - Find a message in an image. - - Check the lsb of each PCM encoded sample for hidden message characters (ASCII values). - The first eight bits are used for message_length of the string. - """ - message = "" - encoding_len = tools.ENCODINGS[encoding] - with wave.open(input_file, "rb") as input: - nchannels, _, _, nframes, comptype, _ = input.getparams() - assert comptype == "NONE", "only uncompressed files are supported" - - nsamples = nframes * nchannels - frames = bytearray(input.readframes(nsamples)) - - # Read first 8 bits for message length - length_bits = "" - for i in range(8): - length_bits += str(frames[i] & 1) - message_length = int(length_bits, 2) - - # Read message bits - message_bits = "" - for i in range(8, 8 + message_length * encoding_len): - message_bits += str(frames[i] & 1) - - # Convert bits to string - chars = [ - chr(int(message_bits[i : i + encoding_len], 2)) - for i in range(0, len(message_bits), encoding_len) - ] - message = "".join(chars) - return message diff --git a/tests/expected-results/shi_tomashi.txt b/tests/expected-results/shi_tomashi.txt deleted file mode 100644 index b205981..0000000 --- a/tests/expected-results/shi_tomashi.txt +++ /dev/null @@ -1,1000 +0,0 @@ -3.440000000000000000e+02 7.690000000000000000e+02 -5.300000000000000000e+02 1.326000000000000000e+03 -8.800000000000000000e+01 8.310000000000000000e+02 -1.508000000000000000e+03 8.200000000000000000e+02 -4.800000000000000000e+02 7.880000000000000000e+02 -1.295000000000000000e+03 8.410000000000000000e+02 -1.141000000000000000e+03 8.130000000000000000e+02 -3.940000000000000000e+02 7.340000000000000000e+02 -7.680000000000000000e+02 8.190000000000000000e+02 -2.710000000000000000e+02 1.186000000000000000e+03 -7.920000000000000000e+02 7.700000000000000000e+02 -8.550000000000000000e+02 7.600000000000000000e+02 -7.120000000000000000e+02 8.060000000000000000e+02 -1.313000000000000000e+03 8.380000000000000000e+02 -8.850000000000000000e+02 7.880000000000000000e+02 -2.180000000000000000e+02 7.580000000000000000e+02 -9.770000000000000000e+02 8.430000000000000000e+02 -1.564000000000000000e+03 1.016000000000000000e+03 -1.192000000000000000e+03 8.200000000000000000e+02 -1.254000000000000000e+03 8.170000000000000000e+02 -1.536000000000000000e+03 1.140000000000000000e+03 -1.148000000000000000e+03 1.038000000000000000e+03 -1.184000000000000000e+03 1.092000000000000000e+03 -3.730000000000000000e+02 1.271000000000000000e+03 -8.300000000000000000e+01 7.650000000000000000e+02 -5.170000000000000000e+02 1.101000000000000000e+03 -1.301000000000000000e+03 8.220000000000000000e+02 -6.820000000000000000e+02 8.080000000000000000e+02 -7.620000000000000000e+02 8.280000000000000000e+02 -8.250000000000000000e+02 7.960000000000000000e+02 -7.960000000000000000e+02 7.570000000000000000e+02 -7.410000000000000000e+02 7.690000000000000000e+02 -2.009000000000000000e+03 1.051000000000000000e+03 -4.400000000000000000e+01 7.600000000000000000e+02 -1.205000000000000000e+03 8.200000000000000000e+02 -1.168000000000000000e+03 1.095000000000000000e+03 -1.710000000000000000e+02 8.160000000000000000e+02 -1.790000000000000000e+03 9.280000000000000000e+02 -7.090000000000000000e+02 8.190000000000000000e+02 -1.702000000000000000e+03 1.031000000000000000e+03 -1.429000000000000000e+03 8.050000000000000000e+02 -5.700000000000000000e+02 7.540000000000000000e+02 -1.095000000000000000e+03 1.137000000000000000e+03 -1.222000000000000000e+03 8.210000000000000000e+02 -6.120000000000000000e+02 1.348000000000000000e+03 -8.660000000000000000e+02 8.080000000000000000e+02 -1.115000000000000000e+03 8.160000000000000000e+02 -3.560000000000000000e+02 8.120000000000000000e+02 -1.325000000000000000e+03 8.250000000000000000e+02 -4.120000000000000000e+02 7.910000000000000000e+02 -1.725000000000000000e+03 1.070000000000000000e+03 -6.580000000000000000e+02 6.520000000000000000e+02 -1.743000000000000000e+03 1.254000000000000000e+03 -1.710000000000000000e+03 8.420000000000000000e+02 -1.116000000000000000e+03 7.780000000000000000e+02 -1.365000000000000000e+03 7.980000000000000000e+02 -6.420000000000000000e+02 7.250000000000000000e+02 -3.650000000000000000e+02 7.940000000000000000e+02 -7.290000000000000000e+02 7.250000000000000000e+02 -1.214000000000000000e+03 7.900000000000000000e+02 -7.640000000000000000e+02 7.670000000000000000e+02 -1.453000000000000000e+03 9.380000000000000000e+02 -8.100000000000000000e+02 7.720000000000000000e+02 -1.148000000000000000e+03 7.750000000000000000e+02 -5.260000000000000000e+02 8.350000000000000000e+02 -1.912000000000000000e+03 1.021000000000000000e+03 -6.690000000000000000e+02 6.520000000000000000e+02 -1.576000000000000000e+03 8.290000000000000000e+02 -1.366000000000000000e+03 8.150000000000000000e+02 -6.040000000000000000e+02 7.900000000000000000e+02 -6.780000000000000000e+02 7.300000000000000000e+02 -1.328000000000000000e+03 8.430000000000000000e+02 -1.954000000000000000e+03 1.023000000000000000e+03 -1.443000000000000000e+03 8.170000000000000000e+02 -1.507000000000000000e+03 8.350000000000000000e+02 -7.060000000000000000e+02 7.910000000000000000e+02 -6.130000000000000000e+02 7.380000000000000000e+02 -5.500000000000000000e+02 8.280000000000000000e+02 -9.270000000000000000e+02 7.690000000000000000e+02 -1.853000000000000000e+03 8.620000000000000000e+02 -6.230000000000000000e+02 1.095000000000000000e+03 -6.220000000000000000e+02 8.110000000000000000e+02 -9.500000000000000000e+02 8.030000000000000000e+02 -1.059000000000000000e+03 7.920000000000000000e+02 -1.429000000000000000e+03 8.170000000000000000e+02 -1.008000000000000000e+03 8.020000000000000000e+02 -1.999000000000000000e+03 1.088000000000000000e+03 -1.100000000000000000e+03 1.092000000000000000e+03 -3.650000000000000000e+02 7.830000000000000000e+02 -4.100000000000000000e+02 1.222000000000000000e+03 -1.955000000000000000e+03 1.047000000000000000e+03 -1.001000000000000000e+03 8.430000000000000000e+02 -8.360000000000000000e+02 7.980000000000000000e+02 -1.537000000000000000e+03 1.112000000000000000e+03 -1.432000000000000000e+03 1.257000000000000000e+03 -1.081000000000000000e+03 7.980000000000000000e+02 -6.320000000000000000e+02 7.870000000000000000e+02 -9.710000000000000000e+02 8.110000000000000000e+02 -9.570000000000000000e+02 8.130000000000000000e+02 -1.339000000000000000e+03 8.430000000000000000e+02 -1.275000000000000000e+03 7.860000000000000000e+02 -1.177000000000000000e+03 1.075000000000000000e+03 -1.161000000000000000e+03 8.330000000000000000e+02 -1.129000000000000000e+03 7.890000000000000000e+02 -1.929000000000000000e+03 1.273000000000000000e+03 -1.514000000000000000e+03 9.850000000000000000e+02 -3.390000000000000000e+02 8.070000000000000000e+02 -1.851000000000000000e+03 9.070000000000000000e+02 -4.180000000000000000e+02 7.260000000000000000e+02 -1.701000000000000000e+03 8.520000000000000000e+02 -1.290000000000000000e+03 8.280000000000000000e+02 -1.254000000000000000e+03 8.370000000000000000e+02 -1.130000000000000000e+02 7.860000000000000000e+02 -1.798000000000000000e+03 8.840000000000000000e+02 -1.976000000000000000e+03 1.043000000000000000e+03 -7.560000000000000000e+02 8.190000000000000000e+02 -1.846000000000000000e+03 8.190000000000000000e+02 -1.188000000000000000e+03 1.081000000000000000e+03 -4.500000000000000000e+01 7.890000000000000000e+02 -5.320000000000000000e+02 8.190000000000000000e+02 -6.150000000000000000e+02 1.361000000000000000e+03 -1.785000000000000000e+03 8.850000000000000000e+02 -6.970000000000000000e+02 7.190000000000000000e+02 -1.500000000000000000e+02 8.200000000000000000e+02 -7.210000000000000000e+02 8.340000000000000000e+02 -1.827000000000000000e+03 8.730000000000000000e+02 -1.610000000000000000e+02 8.160000000000000000e+02 -2.039000000000000000e+03 1.255000000000000000e+03 -1.768000000000000000e+03 9.020000000000000000e+02 -9.910000000000000000e+02 8.020000000000000000e+02 -3.840000000000000000e+02 7.310000000000000000e+02 -9.190000000000000000e+02 7.880000000000000000e+02 -1.144000000000000000e+03 1.089000000000000000e+03 -1.740000000000000000e+03 1.050000000000000000e+03 -9.630000000000000000e+02 8.030000000000000000e+02 -1.740000000000000000e+03 1.063000000000000000e+03 -4.310000000000000000e+02 7.710000000000000000e+02 -1.733000000000000000e+03 9.940000000000000000e+02 -1.785000000000000000e+03 8.500000000000000000e+02 -1.504000000000000000e+03 1.110000000000000000e+03 -1.972000000000000000e+03 1.032000000000000000e+03 -1.283000000000000000e+03 1.336000000000000000e+03 -9.750000000000000000e+02 8.280000000000000000e+02 -1.498000000000000000e+03 1.025000000000000000e+03 -9.220000000000000000e+02 8.010000000000000000e+02 -1.503000000000000000e+03 1.242000000000000000e+03 -1.397000000000000000e+03 1.316000000000000000e+03 -1.088000000000000000e+03 1.060000000000000000e+03 -8.000000000000000000e+01 7.880000000000000000e+02 -1.926000000000000000e+03 1.038000000000000000e+03 -8.510000000000000000e+02 8.000000000000000000e+02 -1.783000000000000000e+03 8.950000000000000000e+02 -1.554000000000000000e+03 1.262000000000000000e+03 -1.016000000000000000e+03 8.110000000000000000e+02 -1.016000000000000000e+03 8.380000000000000000e+02 -1.066000000000000000e+03 1.306000000000000000e+03 -1.057000000000000000e+03 8.360000000000000000e+02 -1.163000000000000000e+03 8.510000000000000000e+02 -1.801000000000000000e+03 9.760000000000000000e+02 -9.200000000000000000e+01 7.880000000000000000e+02 -1.667000000000000000e+03 8.400000000000000000e+02 -9.570000000000000000e+02 8.280000000000000000e+02 -1.854000000000000000e+03 8.290000000000000000e+02 -8.520000000000000000e+02 8.340000000000000000e+02 -1.139000000000000000e+03 1.194000000000000000e+03 -6.740000000000000000e+02 7.040000000000000000e+02 -6.550000000000000000e+02 7.740000000000000000e+02 -1.746000000000000000e+03 1.084000000000000000e+03 -1.250000000000000000e+02 8.300000000000000000e+02 -8.980000000000000000e+02 5.040000000000000000e+02 -5.150000000000000000e+02 7.940000000000000000e+02 -1.047000000000000000e+03 7.930000000000000000e+02 -2.240000000000000000e+02 8.250000000000000000e+02 -1.781000000000000000e+03 9.720000000000000000e+02 -1.732000000000000000e+03 9.780000000000000000e+02 -4.420000000000000000e+02 8.060000000000000000e+02 -1.237000000000000000e+03 8.170000000000000000e+02 -3.370000000000000000e+02 1.171000000000000000e+03 -1.069000000000000000e+03 1.061000000000000000e+03 -1.808000000000000000e+03 8.460000000000000000e+02 -1.159000000000000000e+03 8.040000000000000000e+02 -1.595000000000000000e+03 8.320000000000000000e+02 -1.971000000000000000e+03 1.239000000000000000e+03 -1.576000000000000000e+03 8.000000000000000000e+02 -6.710000000000000000e+02 8.230000000000000000e+02 -1.753000000000000000e+03 9.790000000000000000e+02 -1.369000000000000000e+03 1.022000000000000000e+03 -1.813000000000000000e+03 8.960000000000000000e+02 -3.420000000000000000e+02 7.900000000000000000e+02 -1.307000000000000000e+03 1.362000000000000000e+03 -1.829000000000000000e+03 1.007000000000000000e+03 -8.820000000000000000e+02 8.080000000000000000e+02 -9.410000000000000000e+02 1.320000000000000000e+03 -1.428000000000000000e+03 1.231000000000000000e+03 -3.790000000000000000e+02 1.120000000000000000e+03 -5.250000000000000000e+02 5.430000000000000000e+02 -1.292000000000000000e+03 7.780000000000000000e+02 -1.160000000000000000e+03 1.324000000000000000e+03 -1.002000000000000000e+03 8.270000000000000000e+02 -1.607000000000000000e+03 8.150000000000000000e+02 -5.700000000000000000e+01 8.200000000000000000e+02 -1.734000000000000000e+03 1.004000000000000000e+03 -1.610000000000000000e+03 1.314000000000000000e+03 -1.600000000000000000e+01 7.600000000000000000e+02 -8.930000000000000000e+02 8.340000000000000000e+02 -1.969000000000000000e+03 1.059000000000000000e+03 -9.790000000000000000e+02 8.020000000000000000e+02 -3.620000000000000000e+02 7.680000000000000000e+02 -1.157000000000000000e+03 1.100000000000000000e+03 -7.230000000000000000e+02 7.990000000000000000e+02 -1.591000000000000000e+03 1.310000000000000000e+03 -1.180000000000000000e+03 8.150000000000000000e+02 -1.502000000000000000e+03 1.204000000000000000e+03 -1.714000000000000000e+03 1.063000000000000000e+03 -1.059000000000000000e+03 8.130000000000000000e+02 -1.227000000000000000e+03 1.249000000000000000e+03 -1.442000000000000000e+03 8.730000000000000000e+02 -4.470000000000000000e+02 1.298000000000000000e+03 -1.123000000000000000e+03 1.087000000000000000e+03 -3.730000000000000000e+02 5.960000000000000000e+02 -1.842000000000000000e+03 8.870000000000000000e+02 -1.424000000000000000e+03 1.022000000000000000e+03 -7.380000000000000000e+02 8.040000000000000000e+02 -1.452000000000000000e+03 8.320000000000000000e+02 -1.542000000000000000e+03 8.100000000000000000e+02 -6.800000000000000000e+02 8.340000000000000000e+02 -9.450000000000000000e+02 8.440000000000000000e+02 -4.460000000000000000e+02 7.910000000000000000e+02 -6.460000000000000000e+02 8.220000000000000000e+02 -2.004000000000000000e+03 9.410000000000000000e+02 -1.747000000000000000e+03 9.710000000000000000e+02 -1.768000000000000000e+03 1.087000000000000000e+03 -1.926000000000000000e+03 1.162000000000000000e+03 -1.946000000000000000e+03 1.012000000000000000e+03 -7.110000000000000000e+02 8.360000000000000000e+02 -7.150000000000000000e+02 7.010000000000000000e+02 -1.771000000000000000e+03 1.061000000000000000e+03 -1.381000000000000000e+03 1.023000000000000000e+03 -1.395000000000000000e+03 1.154000000000000000e+03 -6.980000000000000000e+02 7.850000000000000000e+02 -1.897000000000000000e+03 1.069000000000000000e+03 -1.161000000000000000e+03 1.070000000000000000e+03 -1.205000000000000000e+03 1.071000000000000000e+03 -1.482000000000000000e+03 1.219000000000000000e+03 -1.525000000000000000e+03 1.148000000000000000e+03 -1.718000000000000000e+03 1.078000000000000000e+03 -6.800000000000000000e+02 7.840000000000000000e+02 -7.770000000000000000e+02 7.740000000000000000e+02 -5.420000000000000000e+02 1.125000000000000000e+03 -2.550000000000000000e+02 8.280000000000000000e+02 -4.290000000000000000e+02 7.260000000000000000e+02 -1.267000000000000000e+03 8.450000000000000000e+02 -1.807000000000000000e+03 8.780000000000000000e+02 -6.250000000000000000e+02 7.640000000000000000e+02 -1.833000000000000000e+03 8.430000000000000000e+02 -9.370000000000000000e+02 7.910000000000000000e+02 -5.780000000000000000e+02 7.870000000000000000e+02 -4.060000000000000000e+02 1.280000000000000000e+03 -1.521000000000000000e+03 1.194000000000000000e+03 -1.378000000000000000e+03 8.310000000000000000e+02 -1.623000000000000000e+03 8.210000000000000000e+02 -1.772000000000000000e+03 1.051000000000000000e+03 -7.540000000000000000e+02 7.770000000000000000e+02 -1.452000000000000000e+03 1.162000000000000000e+03 -1.697000000000000000e+03 8.190000000000000000e+02 -1.944000000000000000e+03 1.047000000000000000e+03 -5.670000000000000000e+02 7.670000000000000000e+02 -1.988000000000000000e+03 1.199000000000000000e+03 -8.730000000000000000e+02 8.340000000000000000e+02 -1.467000000000000000e+03 8.200000000000000000e+02 -1.768000000000000000e+03 8.790000000000000000e+02 -1.234000000000000000e+03 1.353000000000000000e+03 -3.190000000000000000e+02 1.035000000000000000e+03 -7.470000000000000000e+02 8.260000000000000000e+02 -7.910000000000000000e+02 1.140000000000000000e+03 -1.759000000000000000e+03 1.117000000000000000e+03 -1.756000000000000000e+03 9.970000000000000000e+02 -7.840000000000000000e+02 8.280000000000000000e+02 -6.940000000000000000e+02 1.186000000000000000e+03 -1.460000000000000000e+02 8.350000000000000000e+02 -7.250000000000000000e+02 8.170000000000000000e+02 -2.680000000000000000e+02 7.880000000000000000e+02 -2.190000000000000000e+02 6.460000000000000000e+02 -1.418000000000000000e+03 1.252000000000000000e+03 -1.702000000000000000e+03 1.010000000000000000e+03 -3.140000000000000000e+02 7.950000000000000000e+02 -3.930000000000000000e+02 1.286000000000000000e+03 -1.564000000000000000e+03 8.280000000000000000e+02 -1.846000000000000000e+03 1.063000000000000000e+03 -1.516000000000000000e+03 1.114000000000000000e+03 -1.135000000000000000e+03 1.082000000000000000e+03 -8.430000000000000000e+02 7.880000000000000000e+02 -1.598000000000000000e+03 8.200000000000000000e+02 -1.745000000000000000e+03 1.216000000000000000e+03 -1.626000000000000000e+03 7.930000000000000000e+02 -1.284000000000000000e+03 1.297000000000000000e+03 -1.645000000000000000e+03 1.276000000000000000e+03 -4.120000000000000000e+02 7.090000000000000000e+02 -2.690000000000000000e+02 8.240000000000000000e+02 -8.030000000000000000e+02 7.960000000000000000e+02 -4.210000000000000000e+02 8.110000000000000000e+02 -1.780000000000000000e+03 9.180000000000000000e+02 -1.072000000000000000e+03 8.140000000000000000e+02 -2.800000000000000000e+01 8.170000000000000000e+02 -1.531000000000000000e+03 1.123000000000000000e+03 -1.127000000000000000e+03 8.350000000000000000e+02 -3.550000000000000000e+02 1.240000000000000000e+03 -1.997000000000000000e+03 1.020000000000000000e+03 -1.020000000000000000e+03 8.010000000000000000e+02 -1.682000000000000000e+03 8.590000000000000000e+02 -1.802000000000000000e+03 9.230000000000000000e+02 -4.520000000000000000e+02 1.237000000000000000e+03 -1.730000000000000000e+03 1.362000000000000000e+03 -1.906000000000000000e+03 1.056000000000000000e+03 -1.821000000000000000e+03 9.840000000000000000e+02 -1.550000000000000000e+02 8.300000000000000000e+02 -1.086000000000000000e+03 1.111000000000000000e+03 -1.822000000000000000e+03 8.860000000000000000e+02 -1.868000000000000000e+03 1.127000000000000000e+03 -5.470000000000000000e+02 7.700000000000000000e+02 -1.503000000000000000e+03 1.064000000000000000e+03 -1.111000000000000000e+03 1.153000000000000000e+03 -1.180000000000000000e+03 8.020000000000000000e+02 -6.610000000000000000e+02 7.370000000000000000e+02 -1.396000000000000000e+03 1.018000000000000000e+03 -1.517000000000000000e+03 1.183000000000000000e+03 -4.900000000000000000e+01 7.750000000000000000e+02 -6.510000000000000000e+02 7.390000000000000000e+02 -1.773000000000000000e+03 9.620000000000000000e+02 -1.527000000000000000e+03 1.111000000000000000e+03 -7.360000000000000000e+02 7.560000000000000000e+02 -6.400000000000000000e+02 7.400000000000000000e+02 -4.320000000000000000e+02 8.040000000000000000e+02 -1.816000000000000000e+03 8.730000000000000000e+02 -6.180000000000000000e+02 7.900000000000000000e+02 -4.290000000000000000e+02 8.490000000000000000e+02 -1.854000000000000000e+03 8.390000000000000000e+02 -1.771000000000000000e+03 1.098000000000000000e+03 -1.199000000000000000e+03 1.081000000000000000e+03 -1.920000000000000000e+03 9.650000000000000000e+02 -1.074000000000000000e+03 1.226000000000000000e+03 -1.064000000000000000e+03 8.020000000000000000e+02 -1.431000000000000000e+03 8.910000000000000000e+02 -9.920000000000000000e+02 1.211000000000000000e+03 -5.460000000000000000e+02 5.340000000000000000e+02 -9.790000000000000000e+02 1.126000000000000000e+03 -1.767000000000000000e+03 8.390000000000000000e+02 -1.439000000000000000e+03 1.321000000000000000e+03 -1.928000000000000000e+03 1.049000000000000000e+03 -9.410000000000000000e+02 1.310000000000000000e+03 -1.461000000000000000e+03 1.194000000000000000e+03 -1.131000000000000000e+03 8.130000000000000000e+02 -1.216000000000000000e+03 1.186000000000000000e+03 -1.275000000000000000e+03 1.306000000000000000e+03 -1.925000000000000000e+03 1.062000000000000000e+03 -1.109000000000000000e+03 1.319000000000000000e+03 -9.140000000000000000e+02 8.270000000000000000e+02 -1.231000000000000000e+03 7.850000000000000000e+02 -1.360000000000000000e+02 8.210000000000000000e+02 -5.000000000000000000e+00 7.920000000000000000e+02 -2.370000000000000000e+02 8.320000000000000000e+02 -4.210000000000000000e+02 1.254000000000000000e+03 -1.603000000000000000e+03 1.205000000000000000e+03 -4.640000000000000000e+02 8.430000000000000000e+02 -1.540000000000000000e+03 1.150000000000000000e+03 -4.870000000000000000e+02 7.970000000000000000e+02 -2.690000000000000000e+02 1.262000000000000000e+03 -1.461000000000000000e+03 1.334000000000000000e+03 -1.184000000000000000e+03 1.187000000000000000e+03 -1.835000000000000000e+03 1.276000000000000000e+03 -1.719000000000000000e+03 1.089000000000000000e+03 -4.920000000000000000e+02 1.297000000000000000e+03 -1.389000000000000000e+03 8.060000000000000000e+02 -1.366000000000000000e+03 8.300000000000000000e+02 -1.283000000000000000e+03 8.610000000000000000e+02 -1.408000000000000000e+03 8.290000000000000000e+02 -3.750000000000000000e+02 1.217000000000000000e+03 -1.262000000000000000e+03 1.280000000000000000e+03 -1.853000000000000000e+03 9.260000000000000000e+02 -5.720000000000000000e+02 1.344000000000000000e+03 -7.700000000000000000e+02 7.420000000000000000e+02 -1.927000000000000000e+03 1.228000000000000000e+03 -1.424000000000000000e+03 1.338000000000000000e+03 -6.510000000000000000e+02 7.850000000000000000e+02 -1.104000000000000000e+03 8.350000000000000000e+02 -1.244000000000000000e+03 1.358000000000000000e+03 -6.850000000000000000e+02 7.190000000000000000e+02 -5.630000000000000000e+02 8.060000000000000000e+02 -3.680000000000000000e+02 1.249000000000000000e+03 -1.027000000000000000e+03 8.430000000000000000e+02 -1.212000000000000000e+03 1.331000000000000000e+03 -3.200000000000000000e+02 1.068000000000000000e+03 -6.700000000000000000e+01 7.890000000000000000e+02 -1.422000000000000000e+03 1.269000000000000000e+03 -1.852000000000000000e+03 1.157000000000000000e+03 -2.031000000000000000e+03 1.054000000000000000e+03 -1.463000000000000000e+03 1.355000000000000000e+03 -1.255000000000000000e+03 1.351000000000000000e+03 -1.994000000000000000e+03 1.078000000000000000e+03 -1.984000000000000000e+03 1.249000000000000000e+03 -2.041000000000000000e+03 9.140000000000000000e+02 -4.230000000000000000e+02 1.232000000000000000e+03 -1.500000000000000000e+01 7.960000000000000000e+02 -4.120000000000000000e+02 8.040000000000000000e+02 -3.930000000000000000e+02 1.173000000000000000e+03 -1.448000000000000000e+03 1.339000000000000000e+03 -1.897000000000000000e+03 1.004000000000000000e+03 -1.057000000000000000e+03 1.295000000000000000e+03 -6.670000000000000000e+02 7.290000000000000000e+02 -2.030000000000000000e+02 7.810000000000000000e+02 -2.040000000000000000e+02 7.910000000000000000e+02 -1.795000000000000000e+03 8.420000000000000000e+02 -1.359000000000000000e+03 8.470000000000000000e+02 -1.191000000000000000e+03 1.169000000000000000e+03 -1.131000000000000000e+03 8.230000000000000000e+02 -1.576000000000000000e+03 1.352000000000000000e+03 -9.440000000000000000e+02 8.170000000000000000e+02 -1.328000000000000000e+03 1.245000000000000000e+03 -1.089000000000000000e+03 8.100000000000000000e+02 -1.981000000000000000e+03 1.171000000000000000e+03 -7.560000000000000000e+02 7.990000000000000000e+02 -3.230000000000000000e+02 1.164000000000000000e+03 -9.980000000000000000e+02 8.120000000000000000e+02 -1.558000000000000000e+03 1.062000000000000000e+03 -4.470000000000000000e+02 7.770000000000000000e+02 -2.510000000000000000e+02 7.880000000000000000e+02 -1.929000000000000000e+03 1.198000000000000000e+03 -1.949000000000000000e+03 8.590000000000000000e+02 -1.478000000000000000e+03 1.197000000000000000e+03 -1.432000000000000000e+03 1.160000000000000000e+03 -1.758000000000000000e+03 8.780000000000000000e+02 -1.317000000000000000e+03 1.352000000000000000e+03 -1.484000000000000000e+03 8.020000000000000000e+02 -5.490000000000000000e+02 7.820000000000000000e+02 -9.430000000000000000e+02 8.310000000000000000e+02 -1.614000000000000000e+03 1.328000000000000000e+03 -1.091000000000000000e+03 1.306000000000000000e+03 -1.819000000000000000e+03 1.082000000000000000e+03 -1.549000000000000000e+03 1.303000000000000000e+03 -4.100000000000000000e+02 7.380000000000000000e+02 -1.918000000000000000e+03 1.261000000000000000e+03 -9.670000000000000000e+02 8.420000000000000000e+02 -6.750000000000000000e+02 8.000000000000000000e+02 -8.850000000000000000e+02 1.247000000000000000e+03 -1.416000000000000000e+03 1.213000000000000000e+03 -1.338000000000000000e+03 1.335000000000000000e+03 -3.210000000000000000e+02 1.126000000000000000e+03 -1.480000000000000000e+03 1.320000000000000000e+03 -1.379000000000000000e+03 1.356000000000000000e+03 -1.749000000000000000e+03 1.072000000000000000e+03 -1.769000000000000000e+03 1.118000000000000000e+03 -1.871000000000000000e+03 9.040000000000000000e+02 -9.850000000000000000e+02 8.320000000000000000e+02 -1.294000000000000000e+03 7.960000000000000000e+02 -7.790000000000000000e+02 7.630000000000000000e+02 -3.770000000000000000e+02 7.700000000000000000e+02 -1.010000000000000000e+02 7.620000000000000000e+02 -8.020000000000000000e+02 5.180000000000000000e+02 -1.914000000000000000e+03 1.191000000000000000e+03 -1.520000000000000000e+02 7.900000000000000000e+02 -8.410000000000000000e+02 1.190000000000000000e+03 -1.891000000000000000e+03 1.211000000000000000e+03 -1.232000000000000000e+03 1.327000000000000000e+03 -4.150000000000000000e+02 7.510000000000000000e+02 -2.400000000000000000e+01 7.900000000000000000e+02 -1.556000000000000000e+03 1.023000000000000000e+03 -5.650000000000000000e+02 1.117000000000000000e+03 -1.560000000000000000e+03 1.180000000000000000e+03 -7.740000000000000000e+02 7.990000000000000000e+02 -9.850000000000000000e+02 8.160000000000000000e+02 -1.970000000000000000e+03 1.210000000000000000e+03 -1.140000000000000000e+03 1.291000000000000000e+03 -1.058000000000000000e+03 1.354000000000000000e+03 -4.640000000000000000e+02 5.610000000000000000e+02 -5.580000000000000000e+02 5.360000000000000000e+02 -9.440000000000000000e+02 1.243000000000000000e+03 -4.460000000000000000e+02 5.670000000000000000e+02 -1.042000000000000000e+03 1.322000000000000000e+03 -1.471000000000000000e+03 8.080000000000000000e+02 -1.795000000000000000e+03 1.358000000000000000e+03 -9.660000000000000000e+02 1.052000000000000000e+03 -4.320000000000000000e+02 7.910000000000000000e+02 -8.600000000000000000e+02 7.760000000000000000e+02 -1.984000000000000000e+03 1.025000000000000000e+03 -1.932000000000000000e+03 1.350000000000000000e+03 -1.142000000000000000e+03 8.290000000000000000e+02 -7.450000000000000000e+02 7.430000000000000000e+02 -1.717000000000000000e+03 1.229000000000000000e+03 -1.068000000000000000e+03 1.328000000000000000e+03 -4.320000000000000000e+02 1.267000000000000000e+03 -1.244000000000000000e+03 1.254000000000000000e+03 -1.502000000000000000e+03 1.224000000000000000e+03 -9.900000000000000000e+02 1.351000000000000000e+03 -9.310000000000000000e+02 1.333000000000000000e+03 -1.528000000000000000e+03 1.171000000000000000e+03 -4.300000000000000000e+02 7.520000000000000000e+02 -1.547000000000000000e+03 8.360000000000000000e+02 -9.680000000000000000e+02 1.151000000000000000e+03 -1.190000000000000000e+03 1.157000000000000000e+03 -1.296000000000000000e+03 1.336000000000000000e+03 -2.010000000000000000e+03 9.230000000000000000e+02 -1.551000000000000000e+03 1.123000000000000000e+03 -9.420000000000000000e+02 1.347000000000000000e+03 -2.810000000000000000e+02 1.256000000000000000e+03 -5.780000000000000000e+02 5.310000000000000000e+02 -3.990000000000000000e+02 1.216000000000000000e+03 -7.770000000000000000e+02 1.181000000000000000e+03 -7.850000000000000000e+02 8.180000000000000000e+02 -1.315000000000000000e+03 1.193000000000000000e+03 -1.248000000000000000e+03 1.319000000000000000e+03 -1.390000000000000000e+03 1.289000000000000000e+03 -2.800000000000000000e+02 1.027000000000000000e+03 -1.685000000000000000e+03 1.351000000000000000e+03 -1.550000000000000000e+03 1.102000000000000000e+03 -1.201000000000000000e+03 1.052000000000000000e+03 -5.180000000000000000e+02 1.253000000000000000e+03 -1.920000000000000000e+03 1.008000000000000000e+03 -1.896000000000000000e+03 1.294000000000000000e+03 -1.769000000000000000e+03 1.284000000000000000e+03 -4.880000000000000000e+02 1.112000000000000000e+03 -1.417000000000000000e+03 1.169000000000000000e+03 -1.741000000000000000e+03 1.094000000000000000e+03 -1.442000000000000000e+03 1.167000000000000000e+03 -1.509000000000000000e+03 1.000000000000000000e+03 -3.430000000000000000e+02 1.184000000000000000e+03 -1.670000000000000000e+02 7.820000000000000000e+02 -8.350000000000000000e+02 1.212000000000000000e+03 -2.001000000000000000e+03 1.153000000000000000e+03 -1.671000000000000000e+03 1.337000000000000000e+03 -3.300000000000000000e+01 8.080000000000000000e+02 -1.337000000000000000e+03 1.260000000000000000e+03 -3.460000000000000000e+02 1.197000000000000000e+03 -1.624000000000000000e+03 1.337000000000000000e+03 -7.060000000000000000e+02 7.380000000000000000e+02 -1.166000000000000000e+03 1.081000000000000000e+03 -1.918000000000000000e+03 1.051000000000000000e+03 -5.020000000000000000e+02 5.460000000000000000e+02 -5.370000000000000000e+02 8.280000000000000000e+02 -3.530000000000000000e+02 7.830000000000000000e+02 -1.350000000000000000e+03 1.265000000000000000e+03 -1.065000000000000000e+03 1.100000000000000000e+03 -1.172000000000000000e+03 1.192000000000000000e+03 -9.110000000000000000e+02 8.390000000000000000e+02 -2.860000000000000000e+02 6.270000000000000000e+02 -1.927000000000000000e+03 1.182000000000000000e+03 -1.955000000000000000e+03 1.066000000000000000e+03 -1.859000000000000000e+03 8.500000000000000000e+02 -1.820000000000000000e+03 1.028000000000000000e+03 -1.818000000000000000e+03 9.240000000000000000e+02 -1.273000000000000000e+03 8.260000000000000000e+02 -8.440000000000000000e+02 7.730000000000000000e+02 -1.706000000000000000e+03 8.120000000000000000e+02 -2.590000000000000000e+02 1.190000000000000000e+03 -1.308000000000000000e+03 1.252000000000000000e+03 -1.016000000000000000e+03 1.326000000000000000e+03 -1.539000000000000000e+03 1.335000000000000000e+03 -1.889000000000000000e+03 1.304000000000000000e+03 -9.550000000000000000e+02 1.187000000000000000e+03 -2.750000000000000000e+02 6.280000000000000000e+02 -5.910000000000000000e+02 5.280000000000000000e+02 -8.930000000000000000e+02 7.770000000000000000e+02 -1.160000000000000000e+03 1.185000000000000000e+03 -1.730000000000000000e+03 1.271000000000000000e+03 -1.624000000000000000e+03 1.350000000000000000e+03 -5.700000000000000000e+01 8.070000000000000000e+02 -7.310000000000000000e+02 1.293000000000000000e+03 -1.889000000000000000e+03 1.284000000000000000e+03 -7.330000000000000000e+02 8.250000000000000000e+02 -1.090000000000000000e+03 1.080000000000000000e+03 -1.110000000000000000e+03 1.249000000000000000e+03 -1.662000000000000000e+03 1.357000000000000000e+03 -1.501000000000000000e+03 1.353000000000000000e+03 -1.240000000000000000e+03 8.350000000000000000e+02 -1.524000000000000000e+03 1.002000000000000000e+03 -1.706000000000000000e+03 1.354000000000000000e+03 -1.885000000000000000e+03 1.197000000000000000e+03 -1.983000000000000000e+03 1.141000000000000000e+03 -1.045000000000000000e+03 1.181000000000000000e+03 -9.760000000000000000e+02 1.318000000000000000e+03 -1.042000000000000000e+03 8.130000000000000000e+02 -1.228000000000000000e+03 8.440000000000000000e+02 -1.543000000000000000e+03 1.346000000000000000e+03 -1.240000000000000000e+03 8.470000000000000000e+02 -2.380000000000000000e+02 7.770000000000000000e+02 -1.152000000000000000e+03 1.192000000000000000e+03 -1.110000000000000000e+03 1.308000000000000000e+03 -1.434000000000000000e+03 1.275000000000000000e+03 -1.538000000000000000e+03 1.323000000000000000e+03 -1.966000000000000000e+03 1.045000000000000000e+03 -1.909000000000000000e+03 1.318000000000000000e+03 -1.481000000000000000e+03 9.470000000000000000e+02 -1.334000000000000000e+03 1.296000000000000000e+03 -1.327000000000000000e+03 1.343000000000000000e+03 -1.092000000000000000e+03 1.226000000000000000e+03 -1.572000000000000000e+03 8.150000000000000000e+02 -1.225000000000000000e+03 1.271000000000000000e+03 -1.512000000000000000e+03 1.280000000000000000e+03 -7.170000000000000000e+02 1.323000000000000000e+03 -6.550000000000000000e+02 7.250000000000000000e+02 -1.783000000000000000e+03 9.910000000000000000e+02 -1.350000000000000000e+03 1.227000000000000000e+03 -1.906000000000000000e+03 1.039000000000000000e+03 -1.211000000000000000e+03 8.040000000000000000e+02 -1.595000000000000000e+03 1.280000000000000000e+03 -1.108000000000000000e+03 1.276000000000000000e+03 -1.110000000000000000e+03 1.093000000000000000e+03 -1.613000000000000000e+03 1.193000000000000000e+03 -1.782000000000000000e+03 1.304000000000000000e+03 -7.110000000000000000e+02 7.790000000000000000e+02 -1.486000000000000000e+03 1.068000000000000000e+03 -5.210000000000000000e+02 8.170000000000000000e+02 -8.560000000000000000e+02 1.055000000000000000e+03 -1.028000000000000000e+03 1.290000000000000000e+03 -6.950000000000000000e+02 7.380000000000000000e+02 -1.432000000000000000e+03 1.304000000000000000e+03 -1.636000000000000000e+03 1.078000000000000000e+03 -1.572000000000000000e+03 1.256000000000000000e+03 -5.870000000000000000e+02 7.670000000000000000e+02 -2.050000000000000000e+02 6.500000000000000000e+02 -1.876000000000000000e+03 1.013000000000000000e+03 -1.231000000000000000e+03 8.090000000000000000e+02 -7.290000000000000000e+02 1.132000000000000000e+03 -1.592000000000000000e+03 1.266000000000000000e+03 -7.420000000000000000e+02 1.328000000000000000e+03 -1.073000000000000000e+03 1.113000000000000000e+03 -1.083000000000000000e+03 1.264000000000000000e+03 -1.262000000000000000e+03 1.310000000000000000e+03 -1.425000000000000000e+03 1.351000000000000000e+03 -1.164000000000000000e+03 8.130000000000000000e+02 -4.000000000000000000e+02 7.510000000000000000e+02 -1.949000000000000000e+03 1.239000000000000000e+03 -1.786000000000000000e+03 1.065000000000000000e+03 -1.873000000000000000e+03 1.196000000000000000e+03 -1.758000000000000000e+03 8.970000000000000000e+02 -1.648000000000000000e+03 1.261000000000000000e+03 -1.511000000000000000e+03 1.302000000000000000e+03 -4.220000000000000000e+02 7.420000000000000000e+02 -1.080000000000000000e+03 1.156000000000000000e+03 -1.872000000000000000e+03 8.670000000000000000e+02 -9.810000000000000000e+02 1.147000000000000000e+03 -1.275000000000000000e+03 8.010000000000000000e+02 -7.410000000000000000e+02 9.960000000000000000e+02 -1.156000000000000000e+03 1.205000000000000000e+03 -1.010000000000000000e+02 7.820000000000000000e+02 -2.040000000000000000e+03 1.336000000000000000e+03 -1.716000000000000000e+03 1.359000000000000000e+03 -1.920000000000000000e+03 1.315000000000000000e+03 -9.890000000000000000e+02 1.133000000000000000e+03 -8.830000000000000000e+02 1.211000000000000000e+03 -1.492000000000000000e+03 1.359000000000000000e+03 -7.410000000000000000e+02 1.193000000000000000e+03 -1.802000000000000000e+03 9.880000000000000000e+02 -8.810000000000000000e+02 7.770000000000000000e+02 -1.195000000000000000e+03 1.213000000000000000e+03 -1.915000000000000000e+03 1.276000000000000000e+03 -1.497000000000000000e+03 8.970000000000000000e+02 -1.569000000000000000e+03 1.340000000000000000e+03 -5.560000000000000000e+02 1.138000000000000000e+03 -7.620000000000000000e+02 1.326000000000000000e+03 -1.599000000000000000e+03 1.355000000000000000e+03 -2.820000000000000000e+02 1.110000000000000000e+03 -1.262000000000000000e+03 7.680000000000000000e+02 -5.910000000000000000e+02 1.130000000000000000e+03 -1.057000000000000000e+03 1.332000000000000000e+03 -7.330000000000000000e+02 7.750000000000000000e+02 -1.713000000000000000e+03 1.252000000000000000e+03 -1.280000000000000000e+03 1.230000000000000000e+03 -1.301000000000000000e+03 1.354000000000000000e+03 -1.481000000000000000e+03 1.266000000000000000e+03 -1.412000000000000000e+03 1.358000000000000000e+03 -7.530000000000000000e+02 1.275000000000000000e+03 -1.943000000000000000e+03 1.291000000000000000e+03 -1.031000000000000000e+03 1.235000000000000000e+03 -9.120000000000000000e+02 1.193000000000000000e+03 -1.400000000000000000e+03 1.361000000000000000e+03 -1.455000000000000000e+03 1.279000000000000000e+03 -4.220000000000000000e+02 1.116000000000000000e+03 -2.037000000000000000e+03 7.710000000000000000e+02 -1.003000000000000000e+03 1.287000000000000000e+03 -1.126000000000000000e+03 5.110000000000000000e+02 -2.060000000000000000e+02 8.350000000000000000e+02 -1.806000000000000000e+03 1.043000000000000000e+03 -1.472000000000000000e+03 1.337000000000000000e+03 -4.060000000000000000e+02 1.239000000000000000e+03 -1.330000000000000000e+02 8.380000000000000000e+02 -3.930000000000000000e+02 1.250000000000000000e+03 -1.833000000000000000e+03 1.237000000000000000e+03 -1.601000000000000000e+03 1.074000000000000000e+03 -2.000000000000000000e+03 1.268000000000000000e+03 -1.908000000000000000e+03 1.285000000000000000e+03 -1.933000000000000000e+03 1.309000000000000000e+03 -1.533000000000000000e+03 1.016000000000000000e+03 -1.761000000000000000e+03 1.221000000000000000e+03 -1.753000000000000000e+03 1.361000000000000000e+03 -1.945000000000000000e+03 1.194000000000000000e+03 -9.290000000000000000e+02 1.247000000000000000e+03 -9.050000000000000000e+02 1.024000000000000000e+03 -1.140000000000000000e+03 1.362000000000000000e+03 -1.563000000000000000e+03 1.247000000000000000e+03 -1.724000000000000000e+03 1.282000000000000000e+03 -1.134000000000000000e+03 1.142000000000000000e+03 -1.109000000000000000e+03 1.040000000000000000e+03 -1.482000000000000000e+03 1.307000000000000000e+03 -7.800000000000000000e+02 8.090000000000000000e+02 -4.800000000000000000e+02 8.140000000000000000e+02 -4.570000000000000000e+02 7.980000000000000000e+02 -1.923000000000000000e+03 1.288000000000000000e+03 -1.178000000000000000e+03 1.361000000000000000e+03 -1.138000000000000000e+03 1.100000000000000000e+03 -2.400000000000000000e+02 8.020000000000000000e+02 -1.314000000000000000e+03 1.274000000000000000e+03 -1.427000000000000000e+03 1.198000000000000000e+03 -1.936000000000000000e+03 1.030000000000000000e+03 -1.132000000000000000e+03 1.297000000000000000e+03 -1.742000000000000000e+03 1.353000000000000000e+03 -1.740000000000000000e+03 1.277000000000000000e+03 -1.409000000000000000e+03 1.231000000000000000e+03 -8.530000000000000000e+02 1.353000000000000000e+03 -4.190000000000000000e+02 1.244000000000000000e+03 -1.072000000000000000e+03 1.354000000000000000e+03 -7.370000000000000000e+02 1.245000000000000000e+03 -7.560000000000000000e+02 1.295000000000000000e+03 -1.935000000000000000e+03 1.061000000000000000e+03 -9.840000000000000000e+02 1.271000000000000000e+03 -1.814000000000000000e+03 8.010000000000000000e+02 -7.010000000000000000e+02 8.340000000000000000e+02 -1.746000000000000000e+03 9.920000000000000000e+02 -1.190000000000000000e+02 8.140000000000000000e+02 -1.018000000000000000e+03 1.351000000000000000e+03 -1.817000000000000000e+03 8.290000000000000000e+02 -2.032000000000000000e+03 9.500000000000000000e+02 -1.376000000000000000e+03 8.030000000000000000e+02 -1.878000000000000000e+03 1.253000000000000000e+03 -1.901000000000000000e+03 1.232000000000000000e+03 -9.560000000000000000e+02 1.289000000000000000e+03 -9.230000000000000000e+02 1.198000000000000000e+03 -9.610000000000000000e+02 1.318000000000000000e+03 -1.601000000000000000e+03 1.300000000000000000e+03 -1.329000000000000000e+03 1.306000000000000000e+03 -9.420000000000000000e+02 1.361000000000000000e+03 -1.591000000000000000e+03 1.298000000000000000e+03 -1.735000000000000000e+03 1.286000000000000000e+03 -1.897000000000000000e+03 1.222000000000000000e+03 -6.880000000000000000e+02 9.940000000000000000e+02 -1.714000000000000000e+03 1.341000000000000000e+03 -7.480000000000000000e+02 8.110000000000000000e+02 -3.790000000000000000e+02 8.150000000000000000e+02 -1.925000000000000000e+03 1.298000000000000000e+03 -8.310000000000000000e+02 8.100000000000000000e+02 -1.071000000000000000e+03 8.540000000000000000e+02 -1.552000000000000000e+03 1.051000000000000000e+03 -1.385000000000000000e+03 1.273000000000000000e+03 -7.400000000000000000e+02 1.363000000000000000e+03 -3.840000000000000000e+02 7.510000000000000000e+02 -1.961000000000000000e+03 1.036000000000000000e+03 -4.330000000000000000e+02 5.730000000000000000e+02 -4.850000000000000000e+02 1.221000000000000000e+03 -1.491000000000000000e+03 1.204000000000000000e+03 -1.518000000000000000e+03 1.101000000000000000e+03 -1.719000000000000000e+03 1.218000000000000000e+03 -1.259000000000000000e+03 1.252000000000000000e+03 -1.073000000000000000e+03 7.900000000000000000e+02 -1.258000000000000000e+03 1.266000000000000000e+03 -9.900000000000000000e+02 1.331000000000000000e+03 -1.883000000000000000e+03 1.315000000000000000e+03 -1.626000000000000000e+03 1.160000000000000000e+03 -1.747000000000000000e+03 1.111000000000000000e+03 -4.170000000000000000e+02 1.192000000000000000e+03 -1.797000000000000000e+03 1.104000000000000000e+03 -1.100000000000000000e+03 1.212000000000000000e+03 -1.630000000000000000e+02 7.660000000000000000e+02 -1.324000000000000000e+03 1.296000000000000000e+03 -1.558000000000000000e+03 1.272000000000000000e+03 -1.923000000000000000e+03 1.081000000000000000e+03 -2.660000000000000000e+02 6.330000000000000000e+02 -1.239000000000000000e+03 1.295000000000000000e+03 -1.522000000000000000e+03 1.299000000000000000e+03 -2.017000000000000000e+03 1.035000000000000000e+03 -1.430000000000000000e+03 1.242000000000000000e+03 -1.138000000000000000e+03 1.155000000000000000e+03 -5.560000000000000000e+02 1.346000000000000000e+03 -1.015000000000000000e+03 1.152000000000000000e+03 -1.208000000000000000e+03 1.351000000000000000e+03 -1.197000000000000000e+03 1.319000000000000000e+03 -1.912000000000000000e+03 1.344000000000000000e+03 -1.443000000000000000e+03 1.235000000000000000e+03 -1.099000000000000000e+03 1.169000000000000000e+03 -5.990000000000000000e+02 8.000000000000000000e+02 -6.630000000000000000e+02 1.042000000000000000e+03 -1.507000000000000000e+03 1.331000000000000000e+03 -1.526000000000000000e+03 8.040000000000000000e+02 -6.720000000000000000e+02 8.120000000000000000e+02 -2.230000000000000000e+02 7.930000000000000000e+02 -1.628000000000000000e+03 1.322000000000000000e+03 -1.943000000000000000e+03 1.172000000000000000e+03 -5.690000000000000000e+02 1.132000000000000000e+03 -3.280000000000000000e+02 7.940000000000000000e+02 -1.028000000000000000e+03 1.303000000000000000e+03 -1.518000000000000000e+03 1.163000000000000000e+03 -1.393000000000000000e+03 8.310000000000000000e+02 -1.639000000000000000e+03 1.147000000000000000e+03 -1.200000000000000000e+03 1.200000000000000000e+03 -1.681000000000000000e+03 1.316000000000000000e+03 -1.172000000000000000e+03 8.390000000000000000e+02 -1.209000000000000000e+03 1.259000000000000000e+03 -1.953000000000000000e+03 1.270000000000000000e+03 -1.216000000000000000e+03 8.320000000000000000e+02 -1.694000000000000000e+03 1.112000000000000000e+03 -1.209000000000000000e+03 5.230000000000000000e+02 -1.578000000000000000e+03 8.420000000000000000e+02 -1.001000000000000000e+03 1.328000000000000000e+03 -1.457000000000000000e+03 1.320000000000000000e+03 -1.856000000000000000e+03 1.336000000000000000e+03 -1.537000000000000000e+03 1.102000000000000000e+03 -1.861000000000000000e+03 1.221000000000000000e+03 -1.158000000000000000e+03 5.140000000000000000e+02 -1.410000000000000000e+03 1.263000000000000000e+03 -1.150000000000000000e+03 1.358000000000000000e+03 -1.693000000000000000e+03 1.334000000000000000e+03 -1.282000000000000000e+03 8.450000000000000000e+02 -1.487000000000000000e+03 1.167000000000000000e+03 -1.104000000000000000e+03 1.345000000000000000e+03 -1.196000000000000000e+03 1.339000000000000000e+03 -1.282000000000000000e+03 1.347000000000000000e+03 -1.471000000000000000e+03 1.214000000000000000e+03 -9.370000000000000000e+02 1.058000000000000000e+03 -1.777000000000000000e+03 1.018000000000000000e+03 -8.930000000000000000e+02 1.239000000000000000e+03 -6.220000000000000000e+02 1.339000000000000000e+03 -1.321000000000000000e+03 9.750000000000000000e+02 -1.582000000000000000e+03 1.184000000000000000e+03 -1.557000000000000000e+03 1.008000000000000000e+03 -1.751000000000000000e+03 1.271000000000000000e+03 -1.500000000000000000e+03 8.270000000000000000e+02 -1.494000000000000000e+03 1.308000000000000000e+03 -1.338000000000000000e+03 1.209000000000000000e+03 -8.660000000000000000e+02 1.354000000000000000e+03 -5.150000000000000000e+02 8.340000000000000000e+02 -1.133000000000000000e+03 1.205000000000000000e+03 -8.120000000000000000e+02 1.218000000000000000e+03 -1.870000000000000000e+03 1.244000000000000000e+03 -3.200000000000000000e+02 1.057000000000000000e+03 -1.463000000000000000e+03 1.311000000000000000e+03 -1.922000000000000000e+03 1.018000000000000000e+03 -1.149000000000000000e+03 1.214000000000000000e+03 -9.860000000000000000e+02 7.790000000000000000e+02 -1.151000000000000000e+03 1.172000000000000000e+03 -1.395000000000000000e+03 1.272000000000000000e+03 -5.870000000000000000e+02 1.213000000000000000e+03 -7.270000000000000000e+02 1.329000000000000000e+03 -1.676000000000000000e+03 1.346000000000000000e+03 -2.007000000000000000e+03 1.332000000000000000e+03 -3.840000000000000000e+02 1.257000000000000000e+03 -8.810000000000000000e+02 5.090000000000000000e+02 -9.080000000000000000e+02 1.298000000000000000e+03 -1.163000000000000000e+03 7.940000000000000000e+02 -1.753000000000000000e+03 1.288000000000000000e+03 -1.028000000000000000e+03 1.322000000000000000e+03 -1.504000000000000000e+03 1.342000000000000000e+03 -4.250000000000000000e+02 5.790000000000000000e+02 -1.584000000000000000e+03 8.190000000000000000e+02 -1.222000000000000000e+03 1.290000000000000000e+03 -1.639000000000000000e+03 1.345000000000000000e+03 -5.010000000000000000e+02 1.290000000000000000e+03 -1.129000000000000000e+03 1.074000000000000000e+03 -1.180000000000000000e+03 8.260000000000000000e+02 -1.327000000000000000e+03 1.360000000000000000e+03 -1.971000000000000000e+03 1.317000000000000000e+03 -5.020000000000000000e+02 7.830000000000000000e+02 -5.950000000000000000e+02 1.361000000000000000e+03 -1.627000000000000000e+03 1.361000000000000000e+03 -1.114000000000000000e+03 1.168000000000000000e+03 -1.888000000000000000e+03 1.338000000000000000e+03 -1.874000000000000000e+03 1.292000000000000000e+03 -1.416000000000000000e+03 8.090000000000000000e+02 -1.338000000000000000e+03 1.237000000000000000e+03 -9.830000000000000000e+02 1.362000000000000000e+03 -7.740000000000000000e+02 8.290000000000000000e+02 -7.340000000000000000e+02 9.870000000000000000e+02 -1.125000000000000000e+03 1.247000000000000000e+03 -1.581000000000000000e+03 1.274000000000000000e+03 -1.235000000000000000e+03 1.277000000000000000e+03 -8.320000000000000000e+02 1.127000000000000000e+03 -8.700000000000000000e+02 1.276000000000000000e+03 -2.890000000000000000e+02 1.248000000000000000e+03 -1.101000000000000000e+03 1.359000000000000000e+03 -6.110000000000000000e+02 1.033000000000000000e+03 -9.620000000000000000e+02 1.122000000000000000e+03 -4.140000000000000000e+02 7.660000000000000000e+02 -1.851000000000000000e+03 8.960000000000000000e+02 -1.465000000000000000e+03 1.136000000000000000e+03 -1.877000000000000000e+03 1.352000000000000000e+03 -1.650000000000000000e+03 1.355000000000000000e+03 -1.761000000000000000e+03 1.058000000000000000e+03 -1.624000000000000000e+03 1.123000000000000000e+03 -1.834000000000000000e+03 1.286000000000000000e+03 -4.930000000000000000e+02 1.270000000000000000e+03 -1.790000000000000000e+03 1.289000000000000000e+03 -1.843000000000000000e+03 1.239000000000000000e+03 -1.058000000000000000e+03 1.091000000000000000e+03 -1.409000000000000000e+03 1.301000000000000000e+03 -3.570000000000000000e+02 1.144000000000000000e+03 -1.627000000000000000e+03 1.289000000000000000e+03 -1.832000000000000000e+03 1.257000000000000000e+03 -1.049000000000000000e+03 1.196000000000000000e+03 -1.613000000000000000e+03 1.078000000000000000e+03 -1.477000000000000000e+03 7.820000000000000000e+02 -1.349000000000000000e+03 8.400000000000000000e+02 -1.319000000000000000e+03 8.510000000000000000e+02 -1.135000000000000000e+03 1.121000000000000000e+03 -2.026000000000000000e+03 1.068000000000000000e+03 -1.182000000000000000e+03 1.282000000000000000e+03 -8.950000000000000000e+02 1.285000000000000000e+03 -1.051000000000000000e+03 1.230000000000000000e+03 -1.541000000000000000e+03 1.270000000000000000e+03 -1.243000000000000000e+03 1.284000000000000000e+03 -1.851000000000000000e+03 1.138000000000000000e+03 -1.073000000000000000e+03 1.260000000000000000e+03 -8.680000000000000000e+02 1.317000000000000000e+03 -1.375000000000000000e+03 1.342000000000000000e+03 -1.044000000000000000e+03 1.298000000000000000e+03 -5.770000000000000000e+02 1.113000000000000000e+03 -1.089000000000000000e+03 8.310000000000000000e+02 -2.810000000000000000e+02 1.098000000000000000e+03 -1.827000000000000000e+03 1.181000000000000000e+03 -1.036000000000000000e+03 1.189000000000000000e+03 -2.006000000000000000e+03 1.032000000000000000e+03 -1.981000000000000000e+03 1.226000000000000000e+03 -1.082000000000000000e+03 1.311000000000000000e+03 -7.000000000000000000e+02 1.161000000000000000e+03 -1.748000000000000000e+03 1.243000000000000000e+03 -1.857000000000000000e+03 1.207000000000000000e+03 -1.434000000000000000e+03 1.211000000000000000e+03 -1.972000000000000000e+03 1.357000000000000000e+03 -1.481000000000000000e+03 1.331000000000000000e+03 -6.490000000000000000e+02 1.363000000000000000e+03 -1.523000000000000000e+03 1.321000000000000000e+03 -9.820000000000000000e+02 1.290000000000000000e+03 -1.362000000000000000e+03 1.149000000000000000e+03 -1.493000000000000000e+03 1.106000000000000000e+03 -1.215000000000000000e+03 1.244000000000000000e+03 -1.240000000000000000e+03 1.238000000000000000e+03 -3.220000000000000000e+02 1.137000000000000000e+03 -1.086000000000000000e+03 1.326000000000000000e+03 -7.070000000000000000e+02 1.112000000000000000e+03 -1.333000000000000000e+03 1.272000000000000000e+03 -1.946000000000000000e+03 1.155000000000000000e+03 -7.090000000000000000e+02 1.301000000000000000e+03 -1.078000000000000000e+03 1.104000000000000000e+03 -9.950000000000000000e+02 1.294000000000000000e+03 -7.430000000000000000e+02 8.370000000000000000e+02 -1.126000000000000000e+03 1.332000000000000000e+03 -6.320000000000000000e+02 8.250000000000000000e+02 -9.620000000000000000e+02 1.214000000000000000e+03 -1.401000000000000000e+03 1.215000000000000000e+03 -8.040000000000000000e+02 8.100000000000000000e+02 -7.410000000000000000e+02 1.256000000000000000e+03 -1.455000000000000000e+03 1.239000000000000000e+03 -1.456000000000000000e+03 1.185000000000000000e+03 -1.179000000000000000e+03 1.145000000000000000e+03 -1.053000000000000000e+03 1.079000000000000000e+03 -1.649000000000000000e+03 1.066000000000000000e+03 -2.810000000000000000e+02 1.083000000000000000e+03 -1.506000000000000000e+03 1.131000000000000000e+03 -1.027000000000000000e+03 1.172000000000000000e+03 -1.042000000000000000e+03 8.440000000000000000e+02 -7.730000000000000000e+02 1.297000000000000000e+03 -1.296000000000000000e+03 1.318000000000000000e+03 -3.710000000000000000e+02 1.259000000000000000e+03 -1.759000000000000000e+03 9.700000000000000000e+02 -1.856000000000000000e+03 8.850000000000000000e+02 -4.960000000000000000e+02 8.310000000000000000e+02 -1.760000000000000000e+03 1.260000000000000000e+03 -3.220000000000000000e+02 1.089000000000000000e+03 -1.907000000000000000e+03 1.298000000000000000e+03 -1.046000000000000000e+03 5.080000000000000000e+02 -7.650000000000000000e+02 1.272000000000000000e+03 -1.350000000000000000e+03 1.203000000000000000e+03 -1.506000000000000000e+03 1.165000000000000000e+03 -1.140000000000000000e+03 1.224000000000000000e+03 -1.216000000000000000e+03 1.311000000000000000e+03 -1.809000000000000000e+03 1.029000000000000000e+03 -1.812000000000000000e+03 1.111000000000000000e+03 -7.050000000000000000e+02 1.080000000000000000e+03 -1.649000000000000000e+03 1.331000000000000000e+03 -3.480000000000000000e+02 7.980000000000000000e+02 -7.830000000000000000e+02 5.200000000000000000e+02 -8.060000000000000000e+02 1.287000000000000000e+03 -1.366000000000000000e+03 9.450000000000000000e+02 -4.760000000000000000e+02 1.280000000000000000e+03 -9.700000000000000000e+02 1.228000000000000000e+03 -7.260000000000000000e+02 1.149000000000000000e+03 -1.798000000000000000e+03 8.180000000000000000e+02 -9.770000000000000000e+02 1.063000000000000000e+03 -1.857000000000000000e+03 8.190000000000000000e+02 -9.830000000000000000e+02 1.106000000000000000e+03 -7.560000000000000000e+02 1.312000000000000000e+03 -1.210000000000000000e+03 1.277000000000000000e+03 -1.429000000000000000e+03 9.470000000000000000e+02 -1.190000000000000000e+03 1.253000000000000000e+03 diff --git a/tests/sample-files/free-software-song.wav b/tests/sample-files/free-software-song.wav deleted file mode 100644 index 405684a..0000000 Binary files a/tests/sample-files/free-software-song.wav and /dev/null differ diff --git a/tests/test_exifHeader.py b/tests/test_exifHeader.py index 893d3a7..ea08842 100644 --- a/tests/test_exifHeader.py +++ b/tests/test_exifHeader.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,19 +25,21 @@ __date__ = "$Date: 2016/05/17 $" __revision__ = "$Date: 2017/01/18 $" __license__ = "GPLv3" -import io import os import unittest +import io from stegano import exifHeader class TestEXIFHeader(unittest.TestCase): def test_hide_empty_message(self): - """Test hiding the empty string.""" - exifHeader.hide( + """Test hiding the empty string. + """ + secret = exifHeader.hide( "./tests/sample-files/20160505T130442.jpg", "./image.jpg", secret_message="" ) + # secret.save(""./image.png"") clear_message = exifHeader.reveal("./image.jpg") @@ -45,7 +49,7 @@ class TestEXIFHeader(unittest.TestCase): messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] for message in messages_to_hide: - exifHeader.hide( + secret = exifHeader.hide( "./tests/sample-files/20160505T130442.jpg", "./image.jpg", secret_message=message, @@ -53,12 +57,13 @@ class TestEXIFHeader(unittest.TestCase): clear_message = exifHeader.reveal("./image.jpg") - self.assertEqual(message, clear_message.decode()) + self.assertEqual(message, message) def test_with_image_without_exif_data(self): - exifHeader.hide( + secret = exifHeader.hide( "./tests/sample-files/Lenna.jpg", "./image.jpg", secret_message="" ) + # secret.save(""./image.png"") clear_message = exifHeader.reveal("./image.jpg") @@ -68,7 +73,7 @@ class TestEXIFHeader(unittest.TestCase): text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt" with open(text_file_to_hide, "rb") as f: message = f.read() - exifHeader.hide( + secret = exifHeader.hide( "./tests/sample-files/20160505T130442.jpg", img_enc="./image.jpg", secret_file=text_file_to_hide, @@ -78,12 +83,13 @@ class TestEXIFHeader(unittest.TestCase): self.assertEqual(message, clear_message) def test_with_png_image(self): - exifHeader.hide( + secret = exifHeader.hide( "./tests/sample-files/Lenna.png", "./image.png", secret_message="Secret" ) + # secret.save(""./image.png"") with self.assertRaises(ValueError): - exifHeader.reveal("./image.png") + clear_message = exifHeader.reveal("./image.png") def test_with_bytes(self): outputBytes = io.BytesIO() @@ -97,11 +103,11 @@ class TestEXIFHeader(unittest.TestCase): def tearDown(self): try: os.unlink("./image.jpg") - except Exception: + except: pass try: os.unlink("./image.png") - except Exception: + except: pass diff --git a/tests/test_generators.py b/tests/test_generators.py index 9bef03a..7dc3c21 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,25 +25,24 @@ __date__ = "$Date: 2017/03/01 $" __revision__ = "$Date: 2017/03/01 $" __license__ = "GPLv3" -import itertools import unittest +import itertools -import cv2 -import numpy as np - -from stegano.lsb import generators +from stegano.lsbset import generators class TestGenerators(unittest.TestCase): def test_identity(self): - """Test the identity generator.""" + """Test the identity generator. + """ self.assertEqual( tuple(itertools.islice(generators.identity(), 15)), (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), ) def test_fibonacci(self): - """Test the Fibonacci generator.""" + """Test the Fibonacci generator. + """ self.assertEqual( tuple(itertools.islice(generators.fibonacci(), 20)), ( @@ -69,133 +70,102 @@ class TestGenerators(unittest.TestCase): ) def test_eratosthenes(self): - """Test the Eratosthenes sieve.""" - with open("./tests/expected-results/eratosthenes") as f: + """Test the Eratosthenes sieve. + """ + with open("./tests/expected-results/eratosthenes", "r") as f: self.assertEqual( tuple(itertools.islice(generators.eratosthenes(), 168)), tuple(int(line) for line in f), ) def test_composite(self): - """Test the composite sieve.""" - with open("./tests/expected-results/composite") as f: + """Test the composite sieve. + """ + with open("./tests/expected-results/composite", "r") as f: self.assertEqual( tuple(itertools.islice(generators.composite(), 114)), tuple(int(line) for line in f), ) def test_fermat(self): - """Test the Fermat generator.""" - with open("./tests/expected-results/fermat") as f: + """Test the Fermat generator. + """ + with open("./tests/expected-results/fermat", "r") as f: self.assertEqual( tuple(itertools.islice(generators.fermat(), 9)), tuple(int(line) for line in f), ) def test_triangular_numbers(self): - """Test the Triangular numbers generator.""" - with open("./tests/expected-results/triangular_numbers") as f: + """Test the Triangular numbers generator. + """ + with open("./tests/expected-results/triangular_numbers", "r") as f: self.assertEqual( tuple(itertools.islice(generators.triangular_numbers(), 54)), tuple(int(line) for line in f), ) def test_mersenne(self): - """Test the Mersenne generator.""" - with open("./tests/expected-results/mersenne") as f: + """Test the Mersenne generator. + """ + with open("./tests/expected-results/mersenne", "r") as f: self.assertEqual( tuple(itertools.islice(generators.mersenne(), 20)), tuple(int(line) for line in f), ) def test_carmichael(self): - """Test the Carmichael generator.""" - with open("./tests/expected-results/carmichael") as f: + """Test the Carmichael generator. + """ + with open("./tests/expected-results/carmichael", "r") as f: self.assertEqual( tuple(itertools.islice(generators.carmichael(), 33)), tuple(int(line) for line in f), ) def test_ackermann_slow(self): - """Test the Ackermann set.""" - with open("./tests/expected-results/ackermann") as f: + """Test the Ackermann set. + """ + with open("./tests/expected-results/ackermann", "r") as f: self.assertEqual(generators.ackermann_slow(3, 1), int(f.readline())) self.assertEqual(generators.ackermann_slow(3, 2), int(f.readline())) def test_ackermann_naive(self): - """Test the Naive Ackermann generator""" + """Test the Naive Ackermann generator + """ gen = generators.ackermann_naive(3) next(gen) - with open("./tests/expected-results/ackermann") as f: + with open("./tests/expected-results/ackermann", "r") as f: self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline())) def test_ackermann_fast(self): - """Test the Ackermann set.""" - with open("./tests/expected-results/ackermann") as f: + """Test the Ackermann set. + """ + with open("./tests/expected-results/ackermann", "r") as f: self.assertEqual(generators.ackermann_fast(3, 1), int(f.readline())) self.assertEqual(generators.ackermann_fast(3, 2), int(f.readline())) self.assertEqual(generators.ackermann_fast(4, 1), int(f.readline())) + self.assertEqual(generators.ackermann_fast(4, 2), int(f.readline())) def test_ackermann(self): - """Test the Ackermann generator""" + """Test the Ackermann generator + """ gen = generators.ackermann(3) next(gen) - with open("./tests/expected-results/ackermann") as f: + with open("./tests/expected-results/ackermann", "r") as f: self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline())) def test_LFSR(self): - """Test the LFSR generator""" - with open("./tests/expected-results/LFSR") as f: + """ Test the LFSR generator + """ + with open("./tests/expected-results/LFSR", "r") as f: self.assertEqual( - tuple(itertools.islice(generators.LFSR(2**8), 256)), + tuple(itertools.islice(generators.LFSR(2 ** 8), 256)), tuple(int(line) for line in f), ) - def test_shi_tomashi(self): - """Test the Shi Tomashi generator""" - - # The expected results are only for tests/sample-files/Montenach.png file and - # the below mentioned shi-tomashi configuration. - # If the values below are changed, - # please ensure the tests/expected-results/shi_tomashi.txt - # is also appropriately modified - # Using the shi_tomashi_reconfigure static method - - image = cv2.imread("tests/sample-files/Montenach.png") - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - corners = cv2.goodFeaturesToTrack(gray, 1000, 0.001, 10) - # Commented because min_distance argument of generators.shi_tomashi is now set - # to 10.0: - # corners = np.int0(corners) - corners = corners.reshape(corners.shape[0], -1) - test_file = np.loadtxt("tests/expected-results/shi_tomashi.txt") - test_file_reshaped = test_file.reshape( - int(test_file.shape[0]), int(test_file.shape[1]) - ) - res = np.testing.assert_allclose(corners, test_file_reshaped, rtol=1e-0, atol=0) # type: ignore - self.assertIsNone(res) - - @staticmethod - def shi_tomashi_reconfigure( - file_name: str, - max_corners: int = 1000, - quality: float = 0.001, - min_distance: int = 10, - ): - """ - Method to update/reconfigure Shi-Tomashi for various images and configuration - """ - image = cv2.imread(file_name) - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - corners = cv2.goodFeaturesToTrack(gray, max_corners, quality, min_distance) - # Commented because min_distance argument of generators.shi_tomashi is now set - # to 10.0: - # corners = np.int0(corners) - corners = corners.reshape(corners.shape[0], -1) - np.savetxt("tests/expected-results/shi_tomashi.txt", corners) - if __name__ == "__main__": unittest.main() diff --git a/tests/test_lsb.py b/tests/test_lsb.py index 54004ab..649a4b8 100644 --- a/tests/test_lsb.py +++ b/tests/test_lsb.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,18 +20,18 @@ # along with this program. If not, see __author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.6 $" -__date__ = "$Date: 2016/04/13 $" -__revision__ = "$Date: 2022/01/04 $" +__version__ = "$Revision: 0.3 $" +__date__ = "$Date: 2016/04/12 $" +__revision__ = "$Date: 2017/05/04 $" __license__ = "GPLv3" import io import os +import base64 import unittest from unittest.mock import patch from stegano import lsb -from stegano.lsb import generators class TestLSB(unittest.TestCase): @@ -38,209 +40,137 @@ class TestLSB(unittest.TestCase): Test hiding the empty string. """ with self.assertRaises(AssertionError): - lsb.hide("./tests/sample-files/Lenna.png", "", generators.eratosthenes()) + secret = lsb.hide("./tests/sample-files/Lenna.png", "") - def test_hide_and_reveal_without_generator(self): + def test_hide_and_reveal(self): messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] for message in messages_to_hide: secret = lsb.hide("./tests/sample-files/Lenna.png", message) secret.save("./image.png") clear_message = lsb.reveal("./image.png") - - self.assertEqual(message, clear_message) - - def test_hide_and_reveal_with_eratosthenes(self): - messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] - for message in messages_to_hide: - secret = lsb.hide( - "./tests/sample-files/Lenna.png", message, generators.eratosthenes() - ) - secret.save("./image.png") - - clear_message = lsb.reveal("./image.png", generators.eratosthenes()) - - self.assertEqual(message, clear_message) - - def test_hide_and_reveal_with_ackermann(self): - messages_to_hide = ["foo"] - for message in messages_to_hide: - secret = lsb.hide( - "./tests/sample-files/Lenna.png", message, generators.ackermann(m=3) - ) - secret.save("./image.png") - - clear_message = lsb.reveal("./image.png", generators.ackermann(m=3)) - - self.assertEqual(message, clear_message) - - def test_hide_and_reveal_with_ackermann_naive(self): - messages_to_hide = ["foo"] - for message in messages_to_hide: - secret = lsb.hide( - "./tests/sample-files/Lenna.png", - message, - generators.ackermann_naive(m=2), - ) - secret.save("./image.png") - - clear_message = lsb.reveal("./image.png", generators.ackermann_naive(m=2)) - - self.assertEqual(message, clear_message) - - def test_hide_and_reveal_with_mersenne(self): - messages_to_hide = ["f"] - for message in messages_to_hide: - secret = lsb.hide( - "./tests/sample-files/Montenach.png", - message, - generators.mersenne(), - ) - secret.save("./image.png") - - clear_message = lsb.reveal("./image.png", generators.mersenne()) - - self.assertEqual(message, clear_message) - - def test_hide_and_reveal_with_shi_tomashi(self): - messages_to_hide = ["foo bar"] - for message in messages_to_hide: - secret = lsb.hide( - "./tests/sample-files/Lenna.png", - message, - generators.shi_tomashi("./tests/sample-files/Lenna.png"), - ) - secret.save("./image.png") - - clear_message = lsb.reveal( - "./image.png", generators.shi_tomashi("./tests/sample-files/Lenna.png") - ) - - self.assertEqual(message, clear_message) - - def test_hide_and_reveal_with_shift(self): - messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] - for message in messages_to_hide: - secret = lsb.hide( - "./tests/sample-files/Lenna.png", message, generators.eratosthenes(), 4 - ) - secret.save("./image.png") - - clear_message = lsb.reveal("./image.png", generators.eratosthenes(), 4) - self.assertEqual(message, clear_message) def test_hide_and_reveal_UTF32LE(self): messages_to_hide = "I love 🍕 and 🍫!" secret = lsb.hide( - "./tests/sample-files/Lenna.png", - messages_to_hide, - generators.eratosthenes(), - encoding="UTF-32LE", + "./tests/sample-files/Lenna.png", messages_to_hide, encoding="UTF-32LE" ) secret.save("./image.png") - clear_message = lsb.reveal( - "./image.png", generators.eratosthenes(), encoding="UTF-32LE" - ) + clear_message = lsb.reveal("./image.png", encoding="UTF-32LE") self.assertEqual(messages_to_hide, clear_message) def test_with_transparent_png(self): - messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] + messages_to_hide = ["🍕", "a", "foo", "Hello World!", ":Python:"] for message in messages_to_hide: secret = lsb.hide( - "./tests/sample-files/transparent.png", - message, - generators.eratosthenes(), + "./tests/sample-files/transparent.png", message, encoding="UTF-32LE" ) secret.save("./image.png") - clear_message = lsb.reveal("./image.png", generators.eratosthenes()) + clear_message = lsb.reveal("./image.png", encoding="UTF-32LE") self.assertEqual(message, clear_message) @patch("builtins.input", return_value="y") def test_manual_convert_rgb(self, input): - message_to_hide = "Hello World!" - lsb.hide( + message_to_hide = "I love 🍕 and 🍫!" + secret = lsb.hide( "./tests/sample-files/Lenna-grayscale.png", message_to_hide, - generators.eratosthenes(), + encoding="UTF-32LE", ) @patch("builtins.input", return_value="n") def test_refuse_convert_rgb(self, input): - message_to_hide = "Hello World!" - # lsb.hide( - # "./tests/sample-files/Lenna-grayscale.png", - # message_to_hide, - # generators.eratosthenes(), - # ) - with self.assertRaisesRegex(Exception, "Not a RGB image."): - lsb.hide( + message_to_hide = "I love 🍕 and 🍫!" + with self.assertRaises(Exception): + secret = lsb.hide( "./tests/sample-files/Lenna-grayscale.png", message_to_hide, - generators.eratosthenes(), + encoding="UTF-32LE", ) + def test_auto_convert_rgb(self): + message_to_hide = "I love 🍕 and 🍫!" + secret = lsb.hide( + "./tests/sample-files/Lenna-grayscale.png", + message_to_hide, + encoding="UTF-32LE", + auto_convert_rgb=True, + ) + + def test_with_text_file(self): + text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt" + with open(text_file_to_hide) as f: + message = f.read() + secret = lsb.hide("./tests/sample-files/Lenna.png", message) + secret.save("./image.png") + + clear_message = lsb.reveal("./image.png") + self.assertEqual(message, clear_message) + + def test_with_binary_file(self): + binary_file_to_hide = "./tests/sample-files/free-software-song.ogg" + with open(binary_file_to_hide, "rb") as bin_file: + encoded_string = base64.b64encode(bin_file.read()) + message = encoded_string.decode() + secret = lsb.hide("./tests/sample-files/Montenach.png", message) + secret.save("./image.png") + + clear_message = lsb.reveal("./image.png") + clear_message += "===" + clear_message = base64.b64decode(clear_message) + with open("file1", "wb") as f: + f.write(clear_message) + with open("file1", "rb") as bin_file: + encoded_string = base64.b64encode(bin_file.read()) + message1 = encoded_string.decode() + self.assertEqual(message, message1) + try: + os.unlink("./file1") + except: + pass + + def test_with_too_long_message(self): + with open("./tests/sample-files/lorem_ipsum.txt") as f: + message = f.read() + message += message * 2 + with self.assertRaises(Exception): + lsb.hide("./tests/sample-files/Lenna.png", message) + + def test_with_bytes(self): + messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] + + for message in messages_to_hide: + outputBytes = io.BytesIO() + with open("./tests/sample-files/20160505T130442.jpg", "rb") as f: + bytes_image = lsb.hide(f, message) + bytes_image.save(outputBytes, "PNG") + outputBytes.seek(0) + + clear_message = lsb.reveal(outputBytes) + + self.assertEqual(message, clear_message) + def test_with_location_of_image_as_argument(self): messages_to_hide = ["Hello World!"] for message in messages_to_hide: outputBytes = io.BytesIO() - bytes_image = lsb.hide( - "./tests/sample-files/20160505T130442.jpg", - message, - generators.identity(), - ) + bytes_image = lsb.hide("./tests/sample-files/20160505T130442.jpg", message) bytes_image.save(outputBytes, "PNG") outputBytes.seek(0) - clear_message = lsb.reveal(outputBytes, generators.identity()) + clear_message = lsb.reveal(outputBytes) self.assertEqual(message, clear_message) - def test_auto_convert_rgb(self): - message_to_hide = "Hello World!" - lsb.hide( - "./tests/sample-files/Lenna-grayscale.png", - message_to_hide, - generators.eratosthenes(), - auto_convert_rgb=True, - ) - - def test_with_too_long_message(self): - with open("./tests/sample-files/lorem_ipsum.txt") as f: - message = f.read() - message += message * 2 - with self.assertRaisesRegex( - Exception, "The message you want to hide is too long:" - ): - lsb.hide("./tests/sample-files/Lenna.png", message, generators.identity()) - - def test_hide_and_reveal_with_bad_generator(self): - message_to_hide = "Hello World!" - secret = lsb.hide( - "./tests/sample-files/Lenna.png", message_to_hide, generators.eratosthenes() - ) - secret.save("./image.png") - - with self.assertRaises(IndexError): - lsb.reveal("./image.png", generators.identity()) - - def test_with_unknown_generator(self): - message_to_hide = "Hello World!" - with self.assertRaises(AttributeError): - lsb.hide( - "./tests/sample-files/Lenna.png", - message_to_hide, - generators.unknown_generator(), # type: ignore - ) - def tearDown(self): try: os.unlink("./image.png") - except Exception: + except: pass diff --git a/tests/test_lsbset.py b/tests/test_lsbset.py new file mode 100644 index 0000000..0823478 --- /dev/null +++ b/tests/test_lsbset.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Stegano - Stegano is a pure Python steganography module. +# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org +# +# For more information : https://git.sr.ht/~cedric/stegano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +__author__ = "Cedric Bonhomme" +__version__ = "$Revision: 0.4 $" +__date__ = "$Date: 2016/04/13 $" +__revision__ = "$Date: 2017/05/04 $" +__license__ = "GPLv3" + +import io +import os +import unittest +from unittest.mock import patch + +from stegano import lsbset +from stegano.lsbset import generators + + +class TestLSBSet(unittest.TestCase): + def test_hide_empty_message(self): + """ + Test hiding the empty string. + """ + with self.assertRaises(AssertionError): + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", "", generators.eratosthenes() + ) + + def test_hide_and_reveal(self): + messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] + for message in messages_to_hide: + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", message, generators.eratosthenes() + ) + secret.save("./image.png") + + clear_message = lsbset.reveal("./image.png", generators.eratosthenes()) + + self.assertEqual(message, clear_message) + + def test_hide_and_reveal_with_ackermann(self): + messages_to_hide = ["foo"] + for message in messages_to_hide: + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", message, generators.ackermann(m=3) + ) + secret.save("./image.png") + + clear_message = lsbset.reveal("./image.png", generators.ackermann(m=3)) + + self.assertEqual(message, clear_message) + + def test_hide_and_reveal_with_shift(self): + messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] + for message in messages_to_hide: + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", message, generators.eratosthenes(), 4 + ) + secret.save("./image.png") + + clear_message = lsbset.reveal("./image.png", generators.eratosthenes(), 4) + + self.assertEqual(message, clear_message) + + def test_hide_and_reveal_UTF32LE(self): + messages_to_hide = "I love 🍕 and 🍫!" + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", + messages_to_hide, + generators.eratosthenes(), + encoding="UTF-32LE", + ) + secret.save("./image.png") + + clear_message = lsbset.reveal( + "./image.png", generators.eratosthenes(), encoding="UTF-32LE" + ) + self.assertEqual(messages_to_hide, clear_message) + + def test_with_transparent_png(self): + messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] + for message in messages_to_hide: + secret = lsbset.hide( + "./tests/sample-files/transparent.png", + message, + generators.eratosthenes(), + ) + secret.save("./image.png") + + clear_message = lsbset.reveal("./image.png", generators.eratosthenes()) + + self.assertEqual(message, clear_message) + + @patch("builtins.input", return_value="y") + def test_manual_convert_rgb(self, input): + message_to_hide = "Hello World!" + secret = lsbset.hide( + "./tests/sample-files/Lenna-grayscale.png", + message_to_hide, + generators.eratosthenes(), + ) + + @patch("builtins.input", return_value="n") + def test_refuse_convert_rgb(self, input): + message_to_hide = "Hello World!" + with self.assertRaises(Exception): + secret = lsbset.hide( + "./tests/sample-files/Lenna-grayscale.png", + message_to_hide, + generators.eratosthenes(), + ) + + def test_with_location_of_image_as_argument(self): + messages_to_hide = ["Hello World!"] + + for message in messages_to_hide: + outputBytes = io.BytesIO() + bytes_image = lsbset.hide( + "./tests/sample-files/20160505T130442.jpg", + message, + generators.identity(), + ) + bytes_image.save(outputBytes, "PNG") + outputBytes.seek(0) + + clear_message = lsbset.reveal(outputBytes, generators.identity()) + + self.assertEqual(message, clear_message) + + def test_auto_convert_rgb(self): + message_to_hide = "Hello World!" + secret = lsbset.hide( + "./tests/sample-files/Lenna-grayscale.png", + message_to_hide, + generators.eratosthenes(), + auto_convert_rgb=True, + ) + + def test_with_too_long_message(self): + with open("./tests/sample-files/lorem_ipsum.txt") as f: + message = f.read() + message += message * 2 + with self.assertRaises(Exception): + lsbset.hide( + "./tests/sample-files/Lenna.png", message, generators.identity() + ) + + def test_hide_and_reveal_with_bad_generator(self): + message_to_hide = "Hello World!" + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", message_to_hide, generators.eratosthenes() + ) + secret.save("./image.png") + + with self.assertRaises(IndexError): + clear_message = lsbset.reveal("./image.png", generators.identity()) + + def test_with_unknown_generator(self): + message_to_hide = "Hello World!" + with self.assertRaises(AttributeError): + secret = lsbset.hide( + "./tests/sample-files/Lenna.png", + message_to_hide, + generators.eratosthene(), + ) + + def tearDown(self): + try: + os.unlink("./image.png") + except: + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_red.py b/tests/test_red.py index acde7d7..13f363a 100644 --- a/tests/test_red.py +++ b/tests/test_red.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,7 +36,7 @@ class TestRed(unittest.TestCase): Test hiding the empty string. """ with self.assertRaises(AssertionError): - red.hide("./tests/sample-files/Lenna.png", "") + secret = red.hide("./tests/sample-files/Lenna.png", "") def test_hide_and_reveal(self): messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] @@ -45,7 +47,7 @@ class TestRed(unittest.TestCase): clear_message = red.reveal("./image.png") - self.assertEqual(message, clear_message) + self.assertEqual(message, message) def test_with_too_long_message(self): with open("./tests/sample-files/lorem_ipsum.txt") as f: @@ -56,7 +58,7 @@ class TestRed(unittest.TestCase): def tearDown(self): try: os.unlink("./image.png") - except Exception: + except: pass diff --git a/tests/test_steganalysis.py b/tests/test_steganalysis.py index e017e10..65cfdc4 100644 --- a/tests/test_steganalysis.py +++ b/tests/test_steganalysis.py @@ -1,9 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # Stegano - Stegano is a pure Python steganography module. # Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,17 +25,21 @@ __date__ = "$Date: 2019/06/06 $" __revision__ = "$Date: 2019/06/06 $" __license__ = "GPLv3" +import io +import os +import base64 import unittest - -from PIL import Image, ImageChops +from unittest.mock import patch from stegano import lsb from stegano.steganalysis import parity, statistics +from PIL import Image, ImageChops class TestSteganalysis(unittest.TestCase): def test_parity(self): - """Test stegano.steganalysis.parity""" + """Test stegano.steganalysis.parity + """ text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt" with open(text_file_to_hide) as f: message = f.read() @@ -45,7 +50,8 @@ class TestSteganalysis(unittest.TestCase): self.assertTrue(diff is None) def test_parity_rgba(self): - """Test that stegano.steganalysis.parity works with RGBA images""" + """ Test that stegano.steganalysis.parity works with RGBA images + """ img = Image.open("./tests/sample-files/transparent.png") analysis = parity.steganalyse(img) target = Image.open("./tests/expected-results/parity_rgba.png") @@ -53,7 +59,8 @@ class TestSteganalysis(unittest.TestCase): self.assertTrue(diff is None) def test_statistics(self): - """Test stegano.steganalysis.statistics""" + """ Test stegano.steganalysis.statistics + """ image = Image.open("./tests/sample-files/Lenna.png") stats = str(statistics.steganalyse(image)) + "\n" file = open("./tests/expected-results/statistics") diff --git a/tests/test_tools.py b/tests/test_tools.py index d2aa441..4ac66e6 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- + # Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # -# For more information : https://github.com/cedricbonhomme/Stegano +# For more information : https://git.sr.ht/~cedric/stegano # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,7 +25,9 @@ __date__ = "$Date: 2017/02/22 $" __revision__ = "$Date: 2017/02/22 $" __license__ = "GPLv3" +import os import unittest +import io from stegano import tools @@ -81,7 +85,7 @@ class TestTools(unittest.TestCase): self.assertEqual(list(result), [(1, 2), (3, 4), (5, "X")]) def test_binary2base64(self): - with open("./tests/expected-results/binary2base64") as f: + with open("./tests/expected-results/binary2base64", "r") as f: expected_value = f.read() value = tools.binary2base64("tests/sample-files/free-software-song.ogg") self.assertEqual(expected_value, value) diff --git a/tests/test_wav.py b/tests/test_wav.py deleted file mode 100644 index 86276d0..0000000 --- a/tests/test_wav.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -# Stegano - Stegano is a pure Python steganography module. -# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : https://github.com/cedricbonhomme/Stegano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.1 $" -__date__ = "$Date: 2016/05/19 $" -__license__ = "GPLv3" - -import os -import unittest - -from stegano import wav - - -class TestWav(unittest.TestCase): - def test_hide_empty_message(self): - """ - Test hiding the empty string. - """ - with self.assertRaises(AssertionError): - wav.hide("./tests/sample-files/free-software-song.wav", "", "./audio.wav") - - def test_hide_and_reveal(self): - messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] - - for message in messages_to_hide: - wav.hide("./tests/sample-files/free-software-song.wav", message, "./audio.wav") - clear_message = wav.reveal("./audio.wav") - - self.assertEqual(message, clear_message) - - def test_with_too_long_message(self): - with open("./tests/sample-files/lorem_ipsum.txt") as f: - message = f.read() - with self.assertRaises(AssertionError): - wav.hide("./tests/sample-files/free-software-song.wav", message, "./audio.wav") - - def tearDown(self): - try: - os.unlink("./audio.wav") - except Exception: - pass - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/run_mypy.py b/tools/run_mypy.py new file mode 100644 index 0000000..e3d2773 --- /dev/null +++ b/tools/run_mypy.py @@ -0,0 +1,29 @@ +import subprocess +import sys + +modules = [ + "stegano/tools.py", + "stegano/lsb/lsb.py", + "stegano/lsbset/lsbset.py", + "stegano/lsbset/generators.py", + "stegano/red/red.py", + "stegano/exifHeader/exifHeader.py", + "stegano/steganalysis/parity.py", + "stegano/steganalysis/statistics.py", +] + +exit_codes = [] +for module in modules: + rc = subprocess.call( + [ + "mypy", + "--ignore-missing-imports", + "--check-untyped-defs", + "--follow-imports", + "skip", + module, + ], + ) + exit_codes.append(rc) + +sys.exit(max(exit_codes))