Add per-channel hybrid audio spread spectrum and env feature toggles

Spread spectrum v2: independent per-channel embedding with round-robin
bit distribution, preserving spatial stereo/surround mix. Adaptive chip
tiers (256/512/1024) trade capacity for lossy codec robustness. LFE
channel skipped for 5.1+ layouts. v2 header (20B) with backward-
compatible v0 decode fallback.

Environment toggles (STEGASOO_AUDIO, STEGASOO_VIDEO) gate audio/video
features for minimal builds (e.g. Raspberry Pi image-only). Values:
auto (default, detect deps), 1/true (force on), 0/false (force off).

Web UI fixes: accordion defaults to step 1 on load, chevron arrow
styling, required attribute toggling for audio carrier type switch,
"Images & Mode" renamed to "Reference, Carrier, Mode".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-02-28 11:58:40 -05:00
parent 0248bec813
commit ef5a9ce9cb
41 changed files with 4281 additions and 732 deletions

View File

@@ -19,6 +19,8 @@ Usage:
import base64
import json
import logging
import os
import sys
import traceback
from pathlib import Path
@@ -27,6 +29,24 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
sys.path.insert(0, str(Path(__file__).parent))
# Configure logging for worker subprocess
_log_level = os.environ.get("STEGASOO_LOG_LEVEL", "").strip().upper()
if _log_level and hasattr(logging, _log_level):
logging.basicConfig(
level=getattr(logging, _log_level),
format="[%(asctime)s.%(msecs)03d] [%(levelname)s] [%(name)s] %(message)s",
datefmt="%H:%M:%S",
stream=sys.stderr,
)
elif os.environ.get("STEGASOO_DEBUG", "").strip() in ("1", "true", "yes"):
logging.basicConfig(
level=logging.DEBUG,
format="[%(asctime)s.%(msecs)03d] [%(levelname)s] [%(name)s] %(message)s",
datefmt="%H:%M:%S",
stream=sys.stderr,
)
logger = logging.getLogger("stegasoo.worker")
def _resolve_channel_key(channel_key_param):
"""
@@ -73,6 +93,7 @@ def _get_channel_info(resolved_key):
def encode_operation(params: dict) -> dict:
"""Handle encode operation."""
logger.debug("encode_operation: mode=%s", params.get("embed_mode", "lsb"))
from stegasoo import FilePayload, encode
# Decode base64 inputs
@@ -142,6 +163,7 @@ def _write_decode_progress(progress_file: str | None, percent: int, phase: str)
return
try:
import json
with open(progress_file, "w") as f:
json.dump({"percent": percent, "phase": phase}, f)
except Exception:
@@ -150,6 +172,7 @@ def _write_decode_progress(progress_file: str | None, percent: int, phase: str)
def decode_operation(params: dict) -> dict:
"""Handle decode operation."""
logger.debug("decode_operation: mode=%s", params.get("embed_mode", "auto"))
from stegasoo import decode
progress_file = params.get("progress_file")
@@ -233,6 +256,145 @@ def capacity_check_operation(params: dict) -> dict:
}
def encode_audio_operation(params: dict) -> dict:
"""Handle audio encode operation (v4.3.0)."""
logger.debug("encode_audio_operation: mode=%s", params.get("embed_mode", "audio_lsb"))
from stegasoo import FilePayload, encode_audio
carrier_data = base64.b64decode(params["carrier_b64"])
reference_data = base64.b64decode(params["reference_b64"])
# Optional RSA key
rsa_key_data = None
if params.get("rsa_key_b64"):
rsa_key_data = base64.b64decode(params["rsa_key_b64"])
# Determine payload type
if params.get("file_b64"):
file_data = base64.b64decode(params["file_b64"])
payload = FilePayload(
data=file_data,
filename=params.get("file_name", "file"),
mime_type=params.get("file_mime", "application/octet-stream"),
)
else:
payload = params.get("message", "")
resolved_channel_key = _resolve_channel_key(params.get("channel_key", "auto"))
# Resolve chip_tier from params (None means use default)
chip_tier_val = params.get("chip_tier")
if chip_tier_val is not None:
chip_tier_val = int(chip_tier_val)
stego_audio, stats = encode_audio(
message=payload,
reference_photo=reference_data,
carrier_audio=carrier_data,
passphrase=params.get("passphrase", ""),
pin=params.get("pin"),
rsa_key_data=rsa_key_data,
rsa_password=params.get("rsa_password"),
embed_mode=params.get("embed_mode", "audio_lsb"),
channel_key=resolved_channel_key,
progress_file=params.get("progress_file"),
chip_tier=chip_tier_val,
)
channel_mode, channel_fingerprint = _get_channel_info(resolved_channel_key)
return {
"success": True,
"stego_b64": base64.b64encode(stego_audio).decode("ascii"),
"stats": {
"samples_modified": stats.samples_modified,
"total_samples": stats.total_samples,
"capacity_used": stats.capacity_used,
"bytes_embedded": stats.bytes_embedded,
"sample_rate": stats.sample_rate,
"channels": stats.channels,
"duration_seconds": stats.duration_seconds,
"embed_mode": stats.embed_mode,
},
"channel_mode": channel_mode,
"channel_fingerprint": channel_fingerprint,
}
def decode_audio_operation(params: dict) -> dict:
"""Handle audio decode operation (v4.3.0)."""
logger.debug("decode_audio_operation: mode=%s", params.get("embed_mode", "audio_auto"))
from stegasoo import decode_audio
progress_file = params.get("progress_file")
_write_decode_progress(progress_file, 5, "reading")
stego_data = base64.b64decode(params["stego_b64"])
reference_data = base64.b64decode(params["reference_b64"])
_write_decode_progress(progress_file, 15, "reading")
rsa_key_data = None
if params.get("rsa_key_b64"):
rsa_key_data = base64.b64decode(params["rsa_key_b64"])
resolved_channel_key = _resolve_channel_key(params.get("channel_key", "auto"))
result = decode_audio(
stego_audio=stego_data,
reference_photo=reference_data,
passphrase=params.get("passphrase", ""),
pin=params.get("pin"),
rsa_key_data=rsa_key_data,
rsa_password=params.get("rsa_password"),
embed_mode=params.get("embed_mode", "audio_auto"),
channel_key=resolved_channel_key,
progress_file=progress_file,
)
if result.is_file:
return {
"success": True,
"is_file": True,
"file_b64": base64.b64encode(result.file_data).decode("ascii"),
"filename": result.filename,
"mime_type": result.mime_type,
}
else:
return {
"success": True,
"is_file": False,
"message": result.message,
}
def audio_info_operation(params: dict) -> dict:
"""Handle audio info operation (v4.3.0)."""
from stegasoo import get_audio_info
from stegasoo.audio_steganography import calculate_audio_lsb_capacity
from stegasoo.spread_steganography import calculate_audio_spread_capacity
audio_data = base64.b64decode(params["audio_b64"])
info = get_audio_info(audio_data)
lsb_capacity = calculate_audio_lsb_capacity(audio_data)
spread_capacity = calculate_audio_spread_capacity(audio_data)
return {
"success": True,
"info": {
"sample_rate": info.sample_rate,
"channels": info.channels,
"duration_seconds": round(info.duration_seconds, 2),
"num_samples": info.num_samples,
"format": info.format,
"bit_depth": info.bit_depth,
"capacity_lsb": lsb_capacity,
"capacity_spread": spread_capacity.usable_capacity_bytes,
},
}
def channel_status_operation(params: dict) -> dict:
"""Handle channel status check (v4.0.0)."""
from stegasoo import get_channel_status
@@ -263,6 +425,7 @@ def main():
else:
params = json.loads(input_text)
operation = params.get("operation")
logger.info("Worker handling operation: %s", operation)
if operation == "encode":
output = encode_operation(params)
@@ -274,6 +437,13 @@ def main():
output = capacity_check_operation(params)
elif operation == "channel_status":
output = channel_status_operation(params)
# Audio operations (v4.3.0)
elif operation == "encode_audio":
output = encode_audio_operation(params)
elif operation == "decode_audio":
output = decode_audio_operation(params)
elif operation == "audio_info":
output = audio_info_operation(params)
else:
output = {"success": False, "error": f"Unknown operation: {operation}"}