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:
parent
b2adc63a6a
commit
f7d1b725d7
3 changed files with 110 additions and 12 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
52
tests/test_video_server_resolution.py
Normal file
52
tests/test_video_server_resolution.py
Normal 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
|
||||
Loading…
Reference in a new issue