video: resolve the video server independently of the music active pointer

The video side now uses a configured Plex/Jellyfin on its own (resolve_video_
server), not config_manager.get_active_media_server(). So a music-only server
(Navidrome/SoulSync) never applies to video, and 'Navidrome for music + Plex for
video' works. Order: explicit video pick (video_server setting) → music-active if
video-capable → the single configured one → Plex if both → None. Seam tests cover
each case incl. the mixed setup.
This commit is contained in:
BoulderBadgeDad 2026-06-15 13:50:19 -07:00
parent b2adc63a6a
commit f7d1b725d7
3 changed files with 110 additions and 12 deletions

View file

@ -20,10 +20,9 @@ def register_routes(bp):
def video_libraries():
from . import get_video_db
try:
from core.video.sources import list_video_libraries
from config.settings import config_manager
from core.video.sources import list_video_libraries, resolve_video_server
libs = list_video_libraries() or {"server": None, "movies": [], "tv": []}
server = libs.get("server") or config_manager.get_active_media_server()
server = libs.get("server") or resolve_video_server()
libs["selected"] = (get_video_db().get_library_selection(server)
if server else {"movies": None, "tv": None})
return jsonify(libs)
@ -35,9 +34,11 @@ def register_routes(bp):
def save_video_libraries():
from . import get_video_db
try:
from config.settings import config_manager
from core.video.sources import resolve_video_server
body = request.get_json(silent=True) or {}
server = config_manager.get_active_media_server()
server = resolve_video_server()
if not server:
return jsonify({"error": "no video server"}), 400
get_video_db().set_library_selection(server, body.get("movies"), body.get("tv"))
return jsonify({"status": "saved", "server": server})
except Exception:

View file

@ -63,17 +63,61 @@ def _parse_jf_providers(item) -> dict:
}
def resolve_video_server(db=None):
"""The server the VIDEO side uses — a configured Plex/Jellyfin, resolved
INDEPENDENTLY of the music 'active server' pointer (so e.g. Navidrome-for-music
+ Plex-for-video works, and music-only servers never apply here). Returns
'plex' | 'jellyfin' | None. Order: explicit video pick the music-active one if
it's video-capable → the single configured one → Plex if both → None."""
try:
from config.settings import config_manager
except Exception:
return None
plex_ok = bool((config_manager.get_plex_config() or {}).get("base_url"))
jelly_ok = bool((config_manager.get_jellyfin_config() or {}).get("base_url"))
pref = None
if db is None:
try:
from database.video_database import VideoDatabase
db = VideoDatabase()
except Exception:
db = None
if db is not None:
try:
pref = db.get_setting("video_server")
except Exception:
pref = None
if pref == "plex" and plex_ok:
return "plex"
if pref == "jellyfin" and jelly_ok:
return "jellyfin"
active = config_manager.get_active_media_server()
if active == "plex" and plex_ok:
return "plex"
if active == "jellyfin" and jelly_ok:
return "jellyfin"
if plex_ok and not jelly_ok:
return "plex"
if jelly_ok and not plex_ok:
return "jellyfin"
if plex_ok and jelly_ok:
return "plex"
return None
def _build_source(movies_lib=None, tv_lib=None):
"""Build a media source for the active server, restricted to the named
Movies/TV libraries when given. Reuses the SHARED connection config but
Plex gets a dedicated long-timeout connection for the bulk scan."""
"""Build a media source for the VIDEO server (see resolve_video_server),
restricted to the named Movies/TV libraries when given. Reuses the SHARED
connection config but Plex gets a dedicated long-timeout connection."""
try:
from config.settings import config_manager
except Exception:
logger.exception("video sources: config unavailable")
return None
server = config_manager.get_active_media_server()
server = resolve_video_server()
if server == "plex":
cfg = config_manager.get_plex_config() or {}
@ -103,11 +147,12 @@ def _build_source(movies_lib=None, tv_lib=None):
def _load_selection():
"""The user's Movies/TV library choice for the active server (or {})."""
"""The user's Movies/TV library choice for the VIDEO server (or {})."""
try:
from config.settings import config_manager
from database.video_database import VideoDatabase
server = config_manager.get_active_media_server()
server = resolve_video_server()
if not server:
return {}
return VideoDatabase().get_library_selection(server)
except Exception:
logger.exception("video sources: could not load library selection")

View file

@ -0,0 +1,52 @@
"""Seam tests: the video side resolves its server (Plex/Jellyfin) INDEPENDENTLY
of the music 'active server' pointer so music-only servers never apply and a
mixed setup (Navidrome music + Plex video) works."""
from __future__ import annotations
import config.settings as cs
import pytest
from core.video.sources import resolve_video_server
from database.video_database import VideoDatabase
def _set_cm(monkeypatch, plex, jelly, active):
class CM:
def get_plex_config(self): return {"base_url": "http://p", "token": "t"} if plex else {}
def get_jellyfin_config(self): return {"base_url": "http://j"} if jelly else {}
def get_active_media_server(self): return active
monkeypatch.setattr(cs, "config_manager", CM())
@pytest.fixture()
def vdb(tmp_path):
return VideoDatabase(database_path=str(tmp_path / "v.db"))
def test_plex_only(monkeypatch, vdb):
_set_cm(monkeypatch, True, False, "plex")
assert resolve_video_server(vdb) == "plex"
def test_jellyfin_only(monkeypatch, vdb):
_set_cm(monkeypatch, False, True, "jellyfin")
assert resolve_video_server(vdb) == "jellyfin"
def test_none_when_only_a_music_server(monkeypatch, vdb):
_set_cm(monkeypatch, False, False, "navidrome")
assert resolve_video_server(vdb) is None
def test_independent_of_music_active(monkeypatch, vdb):
# Navidrome is the music server, but Plex is configured → video uses Plex.
_set_cm(monkeypatch, True, False, "navidrome")
assert resolve_video_server(vdb) == "plex"
def test_both_configured_default_then_explicit_pick(monkeypatch, vdb):
_set_cm(monkeypatch, True, True, "plex")
assert resolve_video_server(vdb) == "plex" # falls back to the active one
vdb.set_setting("video_server", "jellyfin")
assert resolve_video_server(vdb) == "jellyfin" # explicit video pick wins