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():
|
def video_libraries():
|
||||||
from . import get_video_db
|
from . import get_video_db
|
||||||
try:
|
try:
|
||||||
from core.video.sources import list_video_libraries
|
from core.video.sources import list_video_libraries, resolve_video_server
|
||||||
from config.settings import config_manager
|
|
||||||
libs = list_video_libraries() or {"server": None, "movies": [], "tv": []}
|
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)
|
libs["selected"] = (get_video_db().get_library_selection(server)
|
||||||
if server else {"movies": None, "tv": None})
|
if server else {"movies": None, "tv": None})
|
||||||
return jsonify(libs)
|
return jsonify(libs)
|
||||||
|
|
@ -35,9 +34,11 @@ def register_routes(bp):
|
||||||
def save_video_libraries():
|
def save_video_libraries():
|
||||||
from . import get_video_db
|
from . import get_video_db
|
||||||
try:
|
try:
|
||||||
from config.settings import config_manager
|
from core.video.sources import resolve_video_server
|
||||||
body = request.get_json(silent=True) or {}
|
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"))
|
get_video_db().set_library_selection(server, body.get("movies"), body.get("tv"))
|
||||||
return jsonify({"status": "saved", "server": server})
|
return jsonify({"status": "saved", "server": server})
|
||||||
except Exception:
|
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):
|
def _build_source(movies_lib=None, tv_lib=None):
|
||||||
"""Build a media source for the active server, restricted to the named
|
"""Build a media source for the VIDEO server (see resolve_video_server),
|
||||||
Movies/TV libraries when given. Reuses the SHARED connection config — but
|
restricted to the named Movies/TV libraries when given. Reuses the SHARED
|
||||||
Plex gets a dedicated long-timeout connection for the bulk scan."""
|
connection config — but Plex gets a dedicated long-timeout connection."""
|
||||||
try:
|
try:
|
||||||
from config.settings import config_manager
|
from config.settings import config_manager
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("video sources: config unavailable")
|
logger.exception("video sources: config unavailable")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
server = config_manager.get_active_media_server()
|
server = resolve_video_server()
|
||||||
|
|
||||||
if server == "plex":
|
if server == "plex":
|
||||||
cfg = config_manager.get_plex_config() or {}
|
cfg = config_manager.get_plex_config() or {}
|
||||||
|
|
@ -103,11 +147,12 @@ def _build_source(movies_lib=None, tv_lib=None):
|
||||||
|
|
||||||
|
|
||||||
def _load_selection():
|
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:
|
try:
|
||||||
from config.settings import config_manager
|
|
||||||
from database.video_database import VideoDatabase
|
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)
|
return VideoDatabase().get_library_selection(server)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("video sources: could not load library selection")
|
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