soulsync/core/video/download_config.py
BoulderBadgeDad 4d45cc614f video downloads (phase 3): source mode + hybrid chain (soulseek/torrent/usenet only)
Video-specific Download Source section: a mode dropdown limited to Soulseek / Torrent /
Usenet / Hybrid (no streaming sources — those are music-only). In hybrid mode, a chain
builder lists the three sources with arrow-reorder + enable toggles (best-first), and
NO album-level/track-level badges (a music-only concept, per spec).

- core/video/download_config.py: pure normalize for download_mode + hybrid_order
  (validates to the 3 sources, dedupes, never empties); stored in video_settings.
- /api/video/downloads/config GET/POST now carries download_mode + hybrid_order
  alongside the folders.
- video-settings.js: dropdown + hybrid rows render/reorder/toggle, show the hybrid
  container only in hybrid mode, save on change. Reuses the .vq-row styling.

7 tests (6 pure + 1 API). Next: the shared slskd connection block (reads/writes the
music config_manager so both sides share one slskd) + confirming the shared Indexers tab.
2026-06-19 00:15:13 -07:00

62 lines
2.1 KiB
Python

"""Video download SOURCE config — which source(s) to download from.
Video only ever uses three sources: **soulseek / torrent / usenet** (no streaming
APIs — those are music-only). ``download_mode`` is one of those three, or
``hybrid``; in hybrid mode ``hybrid_order`` is the ordered chain of enabled sources
the (later-phase) engine tries in turn.
Pure normalize here (no DB, no network) so it's unit-tested in isolation. Stored in
video.db's ``video_settings`` (``download_mode`` + ``hybrid_order`` JSON). Isolated
from the music side — imports only json/typing.
"""
from __future__ import annotations
import json
from typing import Any
SOURCES = ("soulseek", "torrent", "usenet")
MODES = SOURCES + ("hybrid",)
def normalize_mode(value: Any) -> str:
v = str(value or "").strip().lower()
return v if v in MODES else "soulseek"
def normalize_hybrid_order(value: Any) -> list:
"""Ordered, de-duped list of valid sources; defaults to ['soulseek']. Accepts a
JSON string (as stored) or a list (as posted)."""
arr = value
if isinstance(arr, str):
try:
arr = json.loads(arr)
except (ValueError, TypeError):
arr = None
out = []
if isinstance(arr, list):
for s in arr:
s = str(s or "").strip().lower()
if s in SOURCES and s not in out:
out.append(s)
return out or ["soulseek"]
def load(db) -> dict:
return {
"download_mode": normalize_mode(db.get_setting("download_mode")),
"hybrid_order": normalize_hybrid_order(db.get_setting("hybrid_order")),
}
def save(db, body: Any) -> dict:
"""Persist whichever of mode/hybrid_order is present in ``body``."""
body = body if isinstance(body, dict) else {}
if "download_mode" in body:
db.set_setting("download_mode", normalize_mode(body.get("download_mode")))
if "hybrid_order" in body:
db.set_setting("hybrid_order", json.dumps(normalize_hybrid_order(body.get("hybrid_order"))))
return load(db)
__all__ = ["SOURCES", "MODES", "normalize_mode", "normalize_hybrid_order", "load", "save"]