From 268458e91c7085b83aa6be5ba2b913c0c3b1c10c Mon Sep 17 00:00:00 2001 From: root Date: Thu, 22 Jan 2026 17:31:31 -0600 Subject: [PATCH] Fix Jellyfin to use path-specific scanning instead of full library refresh The JellyfinServer.scan_path() method was ignoring the path parameter and calling /Library/Refresh, which triggers a full scan of ALL libraries. This change: - Uses /Library/Media/Updated endpoint which accepts specific paths - Only refreshes the library containing the specified path - Uses modern Authorization header format (MediaBrowser Token="...") - Adds proper JSON payload with Updates array This matches the behavior of the Plex implementation which correctly uses path-specific scanning. Co-Authored-By: Claude Opus 4.5 --- main.py | 2 +- media_server_service.py | 56 +++++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 8647886..104509d 100644 --- a/main.py +++ b/main.py @@ -708,7 +708,7 @@ async def test_connection( headers = {"X-Plex-Token": token} elif type.lower() == "jellyfin": test_url = f"{url}/System/Info/Public" - headers = {"X-MediaBrowser-Token": api_key} + headers = {"Authorization": f'MediaBrowser Token="{api_key}"'} elif type.lower() == "emby": test_url = f"{url}/Library/SelectableMediaFolders" headers = {"X-MediaBrowser-Token": api_key} diff --git a/media_server_service.py b/media_server_service.py index dd155a5..d7d0139 100644 --- a/media_server_service.py +++ b/media_server_service.py @@ -115,17 +115,34 @@ def __init__(self, **kwargs): self.type = "jellyfin" async def scan_path(self, path: str) -> Dict[str, Any]: - """Scan a path in Jellyfin""" + """Scan a specific path in Jellyfin using the Media/Updated endpoint. + + This uses the /Library/Media/Updated endpoint which notifies Jellyfin + about changes to specific paths, rather than triggering a full library scan. + """ headers = { - "X-MediaBrowser-Token": self.api_key + "Authorization": f'MediaBrowser Token="{self.api_key}"', + "Content-Type": "application/json" } - - # Trigger library scan - scan_url = urljoin(self.url, "/Library/Refresh") + + # Use /Library/Media/Updated endpoint for path-specific scanning + scan_url = urljoin(self.url, "/Library/Media/Updated") + + payload = { + "Updates": [ + { + "Path": path, + "UpdateType": "Modified" + } + ] + } + + logger.debug(f"Scanning Jellyfin path: {path}") + async with aiohttp.ClientSession() as session: - async with session.post(scan_url, headers=headers, timeout=30) as response: + async with session.post(scan_url, headers=headers, json=payload, timeout=30) as response: response.raise_for_status() - return {"message": "Scan initiated"} + return {"status": "success", "message": f"Scan initiated for path: {path}"} class EmbyServer(MediaServerBase): def __init__(self, **kwargs): @@ -305,15 +322,28 @@ async def _scan_plex(self, server: PlexServer, path: str, library_type: str) -> async def _scan_jellyfin(self, server: JellyfinServer, path: str) -> Dict[str, Any]: headers = { - "X-MediaBrowser-Token": server.api_key + "Authorization": f'MediaBrowser Token="{server.api_key}"', + "Content-Type": "application/json" } - - # Trigger library scan - scan_url = urljoin(server.url, "/Library/Refresh") + + # Use /Library/Media/Updated endpoint for path-specific scanning + scan_url = urljoin(server.url, "/Library/Media/Updated") + + payload = { + "Updates": [ + { + "Path": path, + "UpdateType": "Modified" + } + ] + } + + logger.debug(f"Scanning Jellyfin path: {path}") + async with aiohttp.ClientSession() as session: - async with session.post(scan_url, headers=headers, timeout=30) as response: + async with session.post(scan_url, headers=headers, json=payload, timeout=30) as response: response.raise_for_status() - return {"message": "Scan initiated"} + return {"status": "success", "message": f"Scan initiated for path: {path}"} async def _scan_emby(self, server: EmbyServer, path: str) -> Dict[str, Any]: headers = {