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

@@ -77,14 +77,10 @@ def init_db():
db = get_db()
# Check if we need to migrate from old single-user schema
cursor = db.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='admin_user'"
)
cursor = db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='admin_user'")
has_old_table = cursor.fetchone() is not None
cursor = db.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='users'"
)
cursor = db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
has_new_table = cursor.fetchone() is not None
if has_old_table and not has_new_table:
@@ -189,9 +185,7 @@ def _ensure_channel_keys_table(db: sqlite3.Connection):
def _ensure_app_settings_table(db: sqlite3.Connection):
"""Ensure app_settings table exists (v4.1.0 migration)."""
cursor = db.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='app_settings'"
)
cursor = db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='app_settings'")
if cursor.fetchone() is None:
db.executescript("""
CREATE TABLE IF NOT EXISTS app_settings (
@@ -212,9 +206,7 @@ def _ensure_app_settings_table(db: sqlite3.Connection):
def get_app_setting(key: str) -> str | None:
"""Get an app-level setting value."""
db = get_db()
row = db.execute(
"SELECT value FROM app_settings WHERE key = ?", (key,)
).fetchone()
row = db.execute("SELECT value FROM app_settings WHERE key = ?", (key,)).fetchone()
return row["value"] if row else None
@@ -384,12 +376,10 @@ def get_user_by_username(username: str) -> User | None:
def get_all_users() -> list[User]:
"""Get all users, admins first, then by creation date."""
db = get_db()
rows = db.execute(
"""
rows = db.execute("""
SELECT id, username, role, created_at FROM users
ORDER BY role = 'admin' DESC, created_at ASC
"""
).fetchall()
""").fetchall()
return [
User(
id=row["id"],
@@ -596,9 +586,7 @@ def create_admin_user(username: str, password: str) -> tuple[bool, str]:
return success, msg
def change_password(
user_id: int, current_password: str, new_password: str
) -> tuple[bool, str]:
def change_password(user_id: int, current_password: str, new_password: str) -> tuple[bool, str]:
"""Change a user's password (requires current password)."""
user = get_user_by_id(user_id)
if not user:
@@ -667,9 +655,7 @@ def delete_user(user_id: int, current_user_id: int) -> tuple[bool, str]:
# Check if this is the last admin
if user.role == ROLE_ADMIN:
db = get_db()
admin_count = db.execute(
"SELECT COUNT(*) FROM users WHERE role = 'admin'"
).fetchone()[0]
admin_count = db.execute("SELECT COUNT(*) FROM users WHERE role = 'admin'").fetchone()[0]
if admin_count <= 1:
return False, "Cannot delete the last admin"
@@ -848,9 +834,7 @@ def save_channel_key(
return False, "This channel key is already saved", None
def update_channel_key_name(
key_id: int, user_id: int, new_name: str
) -> tuple[bool, str]:
def update_channel_key_name(key_id: int, user_id: int, new_name: str) -> tuple[bool, str]:
"""Update the name of a saved channel key."""
new_name = new_name.strip()
if not new_name: