Migrate from jpegio to jpeglib for Python 3.13+ support
- Replace jpegio with jpeglib (jpeglib.to_jpegio compatibility layer)
- Update Python requirement to >=3.11, add 3.13/3.14 classifiers
- AUR: Add install script for user creation and permissions
- AUR: Install frontends to site-packages, create Flask instance dir
- AUR: Use dynamic ${pyver} for systemd WorkingDirectory
Tested: CLI, Web UI (Gunicorn), API (Uvicorn), DCT jpeglib roundtrip
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
42
aur/PKGBUILD
42
aur/PKGBUILD
@@ -1,31 +1,31 @@
|
|||||||
# Maintainer: Aaron D. Lee <your-email@example.com>
|
# Maintainer: Aaron D. Lee <your-email@example.com>
|
||||||
pkgname=stegasoo-git
|
pkgname=stegasoo-git
|
||||||
pkgver=4.1.7.r0.g1acb5a3
|
pkgver=4.2.0.r0.g530e5de
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Secure steganography with hybrid photo + passphrase + PIN authentication"
|
pkgdesc="Secure steganography with hybrid photo + passphrase + PIN authentication"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
url="https://github.com/adlee-was-taken/stegasoo"
|
url="https://github.com/adlee-was-taken/stegasoo"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
|
|
||||||
# NOTE: Requires Python 3.12 (jpegio not compatible with 3.13 yet)
|
# Python 3.11-3.14 supported (uses jpeglib for modern Python compatibility)
|
||||||
depends=(
|
depends=(
|
||||||
'python312' # AUR package - jpegio requires 3.12
|
'python>=3.11'
|
||||||
)
|
)
|
||||||
makedepends=(
|
makedepends=(
|
||||||
'git'
|
'git'
|
||||||
'python312'
|
'python'
|
||||||
|
'python-build'
|
||||||
|
'python-hatchling'
|
||||||
)
|
)
|
||||||
optdepends=(
|
optdepends=(
|
||||||
'zbar: QR code reading from webcam/images'
|
'zbar: QR code reading from webcam/images'
|
||||||
)
|
)
|
||||||
provides=('stegasoo')
|
provides=('stegasoo')
|
||||||
conflicts=('stegasoo')
|
conflicts=('stegasoo')
|
||||||
|
install=stegasoo-git.install
|
||||||
source=("${pkgname}::git+https://github.com/adlee-was-taken/stegasoo.git#branch=main")
|
source=("${pkgname}::git+https://github.com/adlee-was-taken/stegasoo.git#branch=main")
|
||||||
sha256sums=('SKIP')
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
# Python 3.12 from AUR package
|
|
||||||
_python="/usr/bin/python3.12"
|
|
||||||
|
|
||||||
pkgver() {
|
pkgver() {
|
||||||
cd "$pkgname"
|
cd "$pkgname"
|
||||||
git describe --long --tags 2>/dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || \
|
git describe --long --tags 2>/dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || \
|
||||||
@@ -34,32 +34,32 @@ pkgver() {
|
|||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "$pkgname"
|
cd "$pkgname"
|
||||||
|
python -m build --wheel --no-isolation
|
||||||
echo "Using Python: $_python ($($_python --version))"
|
|
||||||
|
|
||||||
# Bootstrap pip for python312
|
|
||||||
$_python -m ensurepip --user --upgrade
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
$_python -m pip install --user build hatchling
|
|
||||||
|
|
||||||
# Build wheel
|
|
||||||
$_python -m build --wheel --no-isolation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
cd "$pkgname"
|
cd "$pkgname"
|
||||||
|
|
||||||
|
# Detect Python version for site-packages path
|
||||||
|
local pyver=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||||
|
|
||||||
# Install to /opt/stegasoo with dedicated venv
|
# Install to /opt/stegasoo with dedicated venv
|
||||||
install -dm755 "$pkgdir/opt/stegasoo"
|
install -dm755 "$pkgdir/opt/stegasoo"
|
||||||
|
|
||||||
# Create fresh venv in package
|
# Create fresh venv in package
|
||||||
$_python -m venv "$pkgdir/opt/stegasoo/venv"
|
python -m venv "$pkgdir/opt/stegasoo/venv"
|
||||||
|
|
||||||
# Install the wheel with all extras
|
# Install the wheel with all extras
|
||||||
local wheel=$(ls dist/*.whl | head -1)
|
local wheel=$(ls dist/*.whl | head -1)
|
||||||
"$pkgdir/opt/stegasoo/venv/bin/pip" install --no-cache-dir "${wheel}[all]"
|
"$pkgdir/opt/stegasoo/venv/bin/pip" install --no-cache-dir "${wheel}[all]"
|
||||||
|
|
||||||
|
# Install frontends (not included in wheel)
|
||||||
|
local site_packages="$pkgdir/opt/stegasoo/venv/lib/python${pyver}/site-packages"
|
||||||
|
cp -r frontends "$site_packages/"
|
||||||
|
|
||||||
|
# Create instance directory for Flask (writable by stegasoo user)
|
||||||
|
install -dm755 "$pkgdir/opt/stegasoo/venv/var/app-instance"
|
||||||
|
|
||||||
# Fix shebangs - replace build-time paths with installed paths
|
# Fix shebangs - replace build-time paths with installed paths
|
||||||
find "$pkgdir/opt/stegasoo/venv/bin" -type f -exec \
|
find "$pkgdir/opt/stegasoo/venv/bin" -type f -exec \
|
||||||
sed -i "s|$pkgdir/opt/stegasoo/venv|/opt/stegasoo/venv|g" {} \;
|
sed -i "s|$pkgdir/opt/stegasoo/venv|/opt/stegasoo/venv|g" {} \;
|
||||||
@@ -86,7 +86,7 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=stegasoo
|
User=stegasoo
|
||||||
WorkingDirectory=/opt/stegasoo/venv/lib/python3.12/site-packages/frontends/web
|
WorkingDirectory=/opt/stegasoo/venv/lib/python${pyver}/site-packages/frontends/web
|
||||||
Environment="PATH=/opt/stegasoo/venv/bin"
|
Environment="PATH=/opt/stegasoo/venv/bin"
|
||||||
ExecStart=/opt/stegasoo/venv/bin/gunicorn -b 127.0.0.1:5000 app:app
|
ExecStart=/opt/stegasoo/venv/bin/gunicorn -b 127.0.0.1:5000 app:app
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
@@ -104,7 +104,7 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=stegasoo
|
User=stegasoo
|
||||||
WorkingDirectory=/opt/stegasoo/venv/lib/python3.12/site-packages/frontends/api
|
WorkingDirectory=/opt/stegasoo/venv/lib/python${pyver}/site-packages/frontends/api
|
||||||
Environment="PATH=/opt/stegasoo/venv/bin"
|
Environment="PATH=/opt/stegasoo/venv/bin"
|
||||||
ExecStart=/opt/stegasoo/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000
|
ExecStart=/opt/stegasoo/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
40
aur/stegasoo-git.install
Normal file
40
aur/stegasoo-git.install
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
post_install() {
|
||||||
|
# Create stegasoo system user if it doesn't exist
|
||||||
|
if ! getent passwd stegasoo >/dev/null; then
|
||||||
|
useradd -r -s /usr/bin/nologin -d /opt/stegasoo stegasoo
|
||||||
|
echo "Created system user 'stegasoo'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set ownership of instance directory for Flask
|
||||||
|
chown -R stegasoo:stegasoo /opt/stegasoo/venv/var/app-instance 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Stegasoo installed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "CLI usage:"
|
||||||
|
echo " stegasoo --help"
|
||||||
|
echo ""
|
||||||
|
echo "To start the web UI:"
|
||||||
|
echo " sudo systemctl start stegasoo-web"
|
||||||
|
echo ""
|
||||||
|
echo "To start the REST API:"
|
||||||
|
echo " sudo systemctl start stegasoo-api"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
post_upgrade() {
|
||||||
|
post_install
|
||||||
|
}
|
||||||
|
|
||||||
|
pre_remove() {
|
||||||
|
# Stop services if running
|
||||||
|
systemctl stop stegasoo-web 2>/dev/null || true
|
||||||
|
systemctl stop stegasoo-api 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
post_remove() {
|
||||||
|
# Optionally remove the stegasoo user
|
||||||
|
# userdel stegasoo 2>/dev/null || true
|
||||||
|
echo "Stegasoo removed. User 'stegasoo' was not removed."
|
||||||
|
echo "To remove: userdel stegasoo"
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ version = "4.2.0"
|
|||||||
description = "Secure steganography with hybrid photo + passphrase + PIN authentication"
|
description = "Secure steganography with hybrid photo + passphrase + PIN authentication"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Aaron D. Lee" }
|
{ name = "Aaron D. Lee" }
|
||||||
]
|
]
|
||||||
@@ -29,9 +29,10 @@ classifiers = [
|
|||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3.14",
|
||||||
"Topic :: Security :: Cryptography",
|
"Topic :: Security :: Cryptography",
|
||||||
"Topic :: Multimedia :: Graphics",
|
"Topic :: Multimedia :: Graphics",
|
||||||
]
|
]
|
||||||
@@ -48,7 +49,7 @@ dependencies = [
|
|||||||
dct = [
|
dct = [
|
||||||
"numpy>=2.0.0",
|
"numpy>=2.0.0",
|
||||||
"scipy>=1.10.0",
|
"scipy>=1.10.0",
|
||||||
"jpegio>=0.2.0",
|
"jpeglib>=1.0.0",
|
||||||
"reedsolo>=1.7.0",
|
"reedsolo>=1.7.0",
|
||||||
]
|
]
|
||||||
cli = [
|
cli = [
|
||||||
@@ -69,7 +70,7 @@ web = [
|
|||||||
# Include DCT support for web UI
|
# Include DCT support for web UI
|
||||||
"numpy>=2.0.0",
|
"numpy>=2.0.0",
|
||||||
"scipy>=1.10.0",
|
"scipy>=1.10.0",
|
||||||
"jpegio>=0.2.0",
|
"jpeglib>=1.0.0",
|
||||||
"reedsolo>=1.7.0",
|
"reedsolo>=1.7.0",
|
||||||
]
|
]
|
||||||
api = [
|
api = [
|
||||||
@@ -81,7 +82,7 @@ api = [
|
|||||||
# Include DCT support for API
|
# Include DCT support for API
|
||||||
"numpy>=2.0.0",
|
"numpy>=2.0.0",
|
||||||
"scipy>=1.10.0",
|
"scipy>=1.10.0",
|
||||||
"jpegio>=0.2.0",
|
"jpeglib>=1.0.0",
|
||||||
"reedsolo>=1.7.0",
|
"reedsolo>=1.7.0",
|
||||||
]
|
]
|
||||||
all = [
|
all = [
|
||||||
@@ -120,7 +121,7 @@ addopts = "-v --cov=stegasoo --cov-report=term-missing"
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = ["py310", "py311", "py312"]
|
target-version = ["py311", "py312", "py313"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
@@ -137,7 +138,7 @@ ignore = ["E501"]
|
|||||||
"src/stegasoo/__init__.py" = ["E402"]
|
"src/stegasoo/__init__.py" = ["E402"]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.10"
|
python_version = "3.11"
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unused_configs = true
|
warn_unused_configs = true
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Why is this cool?
|
|||||||
|
|
||||||
Two approaches depending on what you want:
|
Two approaches depending on what you want:
|
||||||
1. PNG output: We do our own DCT math via scipy (works on any image)
|
1. PNG output: We do our own DCT math via scipy (works on any image)
|
||||||
2. JPEG output: We use jpegio to directly tweak the coefficients (chef's kiss)
|
2. JPEG output: We use jpeglib to directly tweak the coefficients (chef's kiss)
|
||||||
|
|
||||||
v4.1.0 - The "please stop corrupting my data" release:
|
v4.1.0 - The "please stop corrupting my data" release:
|
||||||
- Reed-Solomon error correction (can fix up to 16 byte errors per chunk)
|
- Reed-Solomon error correction (can fix up to 16 byte errors per chunk)
|
||||||
@@ -24,7 +24,7 @@ v3.2.0-patch2 - The "scipy why are you like this" release:
|
|||||||
- Process blocks one at a time with fresh arrays
|
- Process blocks one at a time with fresh arrays
|
||||||
- Yes, it's slower. No, I don't care. Correctness > speed.
|
- Yes, it's slower. No, I don't care. Correctness > speed.
|
||||||
|
|
||||||
Requires: scipy (PNG mode), optionally jpegio (JPEG mode), reedsolo (error correction)
|
Requires: scipy (PNG mode), optionally jpeglib (JPEG mode), reedsolo (error correction)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
@@ -55,14 +55,15 @@ except ImportError:
|
|||||||
dctn = None
|
dctn = None
|
||||||
idctn = None
|
idctn = None
|
||||||
|
|
||||||
# Check for jpegio availability (for proper JPEG mode)
|
# Check for jpeglib availability (for proper JPEG mode)
|
||||||
|
# jpeglib replaces jpegio for Python 3.13+ compatibility
|
||||||
try:
|
try:
|
||||||
import jpegio as jio
|
import jpeglib
|
||||||
|
|
||||||
HAS_JPEGIO = True
|
HAS_JPEGIO = True # Keep variable name for compatibility
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_JPEGIO = False
|
HAS_JPEGIO = False
|
||||||
jio = None
|
jpeglib = None
|
||||||
|
|
||||||
# Import custom exceptions
|
# Import custom exceptions
|
||||||
from .exceptions import InvalidMagicBytesError
|
from .exceptions import InvalidMagicBytesError
|
||||||
@@ -742,7 +743,7 @@ def estimate_capacity_comparison(image_data: bytes) -> dict:
|
|||||||
},
|
},
|
||||||
"jpeg_native": {
|
"jpeg_native": {
|
||||||
"available": HAS_JPEGIO,
|
"available": HAS_JPEGIO,
|
||||||
"note": "Uses jpegio for proper JPEG coefficient embedding",
|
"note": "Uses jpeglib for proper JPEG coefficient embedding",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1082,7 +1083,7 @@ def _embed_jpegio(
|
|||||||
flags = FLAG_COLOR_MODE if color_mode == "color" else 0
|
flags = FLAG_COLOR_MODE if color_mode == "color" else 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
jpeg = jio.read(input_path)
|
jpeg = jpeglib.to_jpegio(jpeglib.read_dct(input_path))
|
||||||
coef_array = jpeg.coef_arrays[JPEGIO_EMBED_CHANNEL]
|
coef_array = jpeg.coef_arrays[JPEGIO_EMBED_CHANNEL]
|
||||||
|
|
||||||
all_positions = _jpegio_get_usable_positions(coef_array)
|
all_positions = _jpegio_get_usable_positions(coef_array)
|
||||||
@@ -1144,7 +1145,7 @@ def _embed_jpegio(
|
|||||||
if progress_file:
|
if progress_file:
|
||||||
_write_progress(progress_file, total_bits, total_bits, "saving")
|
_write_progress(progress_file, total_bits, total_bits, "saving")
|
||||||
|
|
||||||
jio.write(jpeg, output_path)
|
jpeg.write(output_path)
|
||||||
|
|
||||||
with open(output_path, "rb") as f:
|
with open(output_path, "rb") as f:
|
||||||
stego_bytes = f.read()
|
stego_bytes = f.read()
|
||||||
@@ -1392,7 +1393,7 @@ def _extract_jpegio(
|
|||||||
temp_path = _jpegio_bytes_to_file(stego_image, suffix=".jpg")
|
temp_path = _jpegio_bytes_to_file(stego_image, suffix=".jpg")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
jpeg = jio.read(temp_path)
|
jpeg = jpeglib.to_jpegio(jpeglib.read_dct(temp_path))
|
||||||
coef_array = jpeg.coef_arrays[JPEGIO_EMBED_CHANNEL]
|
coef_array = jpeg.coef_arrays[JPEGIO_EMBED_CHANNEL]
|
||||||
|
|
||||||
all_positions = _jpegio_get_usable_positions(coef_array)
|
all_positions = _jpegio_get_usable_positions(coef_array)
|
||||||
|
|||||||
Reference in New Issue
Block a user