Compare commits

..

No commits in common. "master" and "v0.11.2" have entirely different histories.

44 changed files with 719 additions and 1441 deletions

42
.builds/debian.yml Normal file
View file

@ -0,0 +1,42 @@
image: debian/bullseye
sources:
- https://git.sr.ht/~cedric/stegano
packages:
- python3
- python3-dev
- python3-pip
- python3-setuptools
- python3-venv
- rustc
- cargo
- libffi-dev
- libssl-dev
- libjpeg-dev
- libpng-dev
- libfreetype6-dev
- ffmpeg
- libsm6
- libxext6
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 stegano --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings.
poetry run flake8 stegano --count --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 mypy .

View file

@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: [3.8, 3.9]
steps:
- uses: actions/checkout@v1
@ -22,14 +22,14 @@ jobs:
- name: Install dependencies
run: |
pip install poetry
poetry install --with dev
poetry install
- 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
poetry run flake8 stegano --count --max-complexity=15 --max-line-length=127 --statistics
- name: Test with pytest
run: |
@ -37,6 +37,6 @@ jobs:
env:
testing: actions
# - name: Type check with mypy
# run: |
# poetry run mypy .
- name: Type check with mypy
run: |
poetry run mypy .

View file

@ -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

View file

@ -1,6 +1,11 @@
ci:
autoupdate_schedule: monthly
repos:
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.13.0
hooks:
- id: django-upgrade
args: [--target-version, '4.2']
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
@ -14,23 +19,25 @@ repos:
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/asottile/blacken-docs
rev: 1.13.0
hooks:
- id: blacken-docs
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
rev: 4.0.1
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-implicit-str-concat
args: ["--max-line-length=125", "--ignore=E203"]
args: ["--max-line-length=125"]
- 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
- repo: https://github.com/trailofbits/pip-audit
rev: v2.5.6
hooks:
- id: pip-audit

View file

@ -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

View file

@ -1,259 +1,222 @@
## 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.
* 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.
* 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,
* 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).
* 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.
* 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
* 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 +224,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 +297,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.

View file

@ -7,17 +7,14 @@
## Contributors
- Alexander Treml - https://github.com/AlexanderTreml
- Adrien Cosson - https://cosson.io
- Andrew Roberts <andy.roberts.uk@gmail.com>
- Christophe Goessen - https://github.com/cgoessen
- Flavien Roux - https://github.com/FlavienRx
- Maxwell Gerber - https://github.com/maxwellgerber
- Mickaël Schoentgen <mschoentgen@nuxeo.com>
- Nejdet Çağdaş Yücesoy <nejdetyucesoy@gmail.com>
- panni <panni@fragstore.net>
- Peter Justin <peter@peterjustin.me>
- thundersparkf - https://github.com/thundersparkf
And thank you to the testers!

View file

@ -1,8 +1,9 @@
# Stegano
[![builds.sr.ht status](https://builds.sr.ht/~cedric/stegano.svg)](https://builds.sr.ht/~cedric/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)
[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
@ -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-2023 [Cédric Bonhomme](https://www.cedricbonhomme.org)
For more information, [the list of authors and contributors](CONTRIBUTORS.md) is available.

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 Cédric Bonhomme - https://www.cedricbonhomme.org
#
# For more information : https://github.com/cedricbonhomme/Stegano
#

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 Cédric Bonhomme - https://www.cedricbonhomme.org
#
# For more information : https://github.com/cedricbonhomme/Stegano
#

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 Cédric Bonhomme - https://www.cedricbonhomme.org
#
# For more information : https://github.com/cedricbonhomme/Stegano
#

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 Cédric Bonhomme - https://www.cedricbonhomme.org
#
# For more information : https://github.com/cedricbonhomme/Stegano
#

View file

@ -34,7 +34,7 @@ master_doc = "index"
# General information about the project.
project = "Stegano"
copyright = "2010-2025, Cédric Bonhomme"
copyright = "2010-2023, Cédric Bonhomme"
author = "Cédric Bonhomme <cedric@cedricbonhomme.org>"
# The version info for the project you're documenting, acts as replacement for

View file

@ -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
@ -49,7 +53,7 @@ Tutorial
steganalysis
You can have a look at the
`unit tests <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_.
`unit tests <https://git.sr.ht/~cedric/stegano/tree/master/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

View file

@ -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

View file

@ -2,7 +2,7 @@ Using Stegano as a Python module
================================
You can find more examples in the
`unit tests directory <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_.
`unit tests directory <https://git.sr.ht/~cedric/stegano/tree/master/tests>`_.
LSB method
----------

1263
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,79 +1,67 @@
[build-system]
requires = ["poetry-core>=2.0"]
build-backend = "poetry.core.masonry.api"
[project]
[tool.poetry]
name = "stegano"
version = "2.0.0"
version = "0.11.2"
description = "A pure Python Steganography module."
authors = [
{name = "Cédric Bonhomme", email= "cedric@cedricbonhomme.org"}
"Cédric Bonhomme <cedric@cedricbonhomme.org>"
]
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",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"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-red = "bin.red:main"
stegano-steganalysis-parity = "bin.parity:main"
stegano-steganalysis-statistics = "bin.statistics:main"
[tool.poetry.dependencies]
python = ">=3.8.1,<3.12"
pillow = "^9.5.0"
piexif = "^1.1.3"
crayons = "^0.4.0"
opencv-python = "^4.7.0.72"
[tool.poetry.group.dev.dependencies]
mypy = "^1.8.0"
mypy = "^1.2.0"
flake8 = "^6.0.0"
nose2 = "^0.14.0"
nose2 = "^0.12.0"
Sphinx = "^6.2.1"
pre-commit = "^3.6.0"
[tool.poetry.group.dev]
optional = true
[build-system]
requires = ["poetry>=1.3.2"]
build-backend = "poetry.masonry.api"
[tool.mypy]
python_version = "3.13"
python_version = "3.10"
check_untyped_defs = true
ignore_errors = false
ignore_missing_imports = true
@ -87,12 +75,7 @@ warn_unreachable = true
show_error_context = true
pretty = true
exclude = "build|dist|docs"
exclude = "build|dist|docs|stegano.egg-info"
[tool.isort]
profile = "black"
[tool.flake8]
ignore = ["E203"]

View file

@ -1,5 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from . import exifHeader, lsb, red, steganalysis
from . import red
from . import exifHeader
from . import lsb
from . import steganalysis
__all__ = ["red", "exifHeader", "lsb", "steganalysis"]

View file

@ -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 <http://www.gnu.org/licenses/>
__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)

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .exifHeader import hide, reveal

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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
@ -40,7 +40,7 @@ def hide(
from zlib import compress
if secret_file is not None:
with open(secret_file, "rb") as f:
with open(secret_file) as f:
secret_message = f.read()
try:

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .lsb import hide, reveal

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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
@ -67,10 +67,9 @@ def reveal(
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)
revealer = tools.Revealer(encoded_image, encoding)
width = revealer.encoded_image.width
if not generator:

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .red import hide, reveal

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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 +23,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 +40,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 +70,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

View file

@ -1 +1,2 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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 +23,6 @@ __date__ = "$Date: 2010/10/01 $"
__revision__ = "$Date: 2019/06/06 $"
__license__ = "GPLv3"
from typing import cast
from PIL import Image
@ -36,10 +34,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:

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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 +26,7 @@ __license__ = "GPLv3"
import base64
import itertools
from functools import reduce
from typing import IO, List, Union, cast
from typing import IO, List, Union
from PIL import Image
@ -101,12 +101,11 @@ def base642binary(b64_fname: str) -> bytes:
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
@ -158,15 +157,8 @@ class Hider:
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)
)
# Get the colour component.
r, g, b, *a = self.encoded_image.getpixel(coordinate)
# Change the Least Significant Bit of each colour component.
r = setlsb(r, self._message_bits[self._index])
@ -183,26 +175,17 @@ class Hider:
class Revealer:
def __init__(
self,
encoded_image: Union[str, IO[bytes]],
encoding: str = "UTF-8",
close_file: bool = True,
):
def __init__(self, encoded_image: Union[str, IO[bytes]], encoding: str = "UTF-8"):
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),
)
# pixel = [r, g, b] or [r,g,b,a]
pixel = self.encoded_image.getpixel(coordinate)
if self.encoded_image.mode == "RGBA":
pixel = pixel[:3] # ignore the alpha
@ -222,9 +205,12 @@ class Revealer:
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()
self.secret_message = "".join(self._bitab)[
len(str(self._limit)) + 1 : # noqa: E203
]
self.encoded_image.close()
return True
else:
return False

View file

@ -1,5 +0,0 @@
#!/usr/bin/env python
from .wav import hide, reveal
__all__ = ["hide", "reveal"]

View file

@ -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 <http://www.gnu.org/licenses/>
__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

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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
@ -174,8 +174,9 @@ class TestGenerators(unittest.TestCase):
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)
self.assertIsNone(
np.testing.assert_allclose(corners, test_file_reshaped, rtol=1e-0, atol=0)
)
@staticmethod
def shi_tomashi_reconfigure(

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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

View file

@ -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
@ -26,10 +27,9 @@ __license__ = "GPLv3"
import unittest
from PIL import Image, ImageChops
from stegano import lsb
from stegano.steganalysis import parity, statistics
from PIL import Image, ImageChops
class TestSteganalysis(unittest.TestCase):

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Copyright (C) 2010-2023 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

View file

@ -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 <http://www.gnu.org/licenses/>
__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()