From c9ffefed70a99b8930c780c9347e36ac25876f41 Mon Sep 17 00:00:00 2001 From: mani Date: Fri, 9 Jan 2026 00:15:08 +0100 Subject: [PATCH] Fix Xbox proxy: Filter REQUEST body instead of response BREAKING FIX: DeviceProfile is sent FROM client TO server in request body, not returned in response. This was the bug preventing the filter from working. Changes: - Filter DeviceProfile in POST request body before sending to Jellyfin - Add extensive debug logging to track filtering process - Remove unnecessary response filtering (DeviceProfile not in response) - Simplify response handling (always stream) Now logs: - 'Xbox PlaybackInfo request detected' when Xbox client detected - 'Filtering Xbox DeviceProfile in REQUEST' when filtering - 'No DeviceProfile found' if profile missing (debug info) --- docker/jellyfin-xbox-proxy/xbox-filter.py | 56 +++++++++++------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/docker/jellyfin-xbox-proxy/xbox-filter.py b/docker/jellyfin-xbox-proxy/xbox-filter.py index 647268b..06b312d 100644 --- a/docker/jellyfin-xbox-proxy/xbox-filter.py +++ b/docker/jellyfin-xbox-proxy/xbox-filter.py @@ -73,44 +73,42 @@ def proxy(path): headers['X-Forwarded-Host'] = request.host headers['X-Real-IP'] = request.remote_addr + # Get request body + request_data = request.get_data() + + # Filter REQUEST body for Xbox PlaybackInfo (DeviceProfile is sent TO server) + if is_xbox and is_playback_info: + logger.info(f"Xbox PlaybackInfo request detected: {path}, User-Agent: {user_agent}") + if request_data: + try: + data = json.loads(request_data) + if "DeviceProfile" in data: + logger.info(f"Filtering Xbox DeviceProfile in REQUEST") + original_profile = data["DeviceProfile"] + filtered_profile = filter_codecs(data["DeviceProfile"]) + data["DeviceProfile"] = filtered_profile + request_data = json.dumps(data).encode('utf-8') + headers['Content-Length'] = str(len(request_data)) + logger.info("DeviceProfile filtered successfully in REQUEST") + else: + logger.warning("No DeviceProfile found in REQUEST body") + except Exception as e: + logger.error(f"Error filtering request: {e}", exc_info=True) + else: + logger.warning("No request body found for PlaybackInfo") + resp = requests.request( method=request.method, url=url, headers=headers, - data=request.get_data(), + data=request_data, cookies=request.cookies, allow_redirects=False, stream=True # Support large file streaming ) - # Filter response for Xbox PlaybackInfo requests - if is_xbox and is_playback_info and resp.status_code == 200: - try: - # Read full response for JSON manipulation - data = resp.json() - - # Filter DeviceProfile in request body (if sent back) - if "DeviceProfile" in data: - logger.info(f"Filtering Xbox DeviceProfile for User-Agent: {user_agent}") - data["DeviceProfile"] = filter_codecs(data["DeviceProfile"]) - - # Filter MediaSources DirectPlayProfiles (generated server-side) - if "MediaSources" in data: - for source in data["MediaSources"]: - if "TranscodingUrl" in source: - # Already filtered by Jellyfin - pass - - response_data = json.dumps(data) - response = Response(response_data, status=resp.status_code) - response.headers['Content-Type'] = 'application/json' - except Exception as e: - logger.error(f"Error filtering response: {e}") - # Fallback to streaming response - response = Response(resp.iter_content(chunk_size=8192), status=resp.status_code) - else: - # Stream response for large files (videos, etc.) - response = Response(resp.iter_content(chunk_size=8192), status=resp.status_code) + # Stream all responses (no need to filter response, DeviceProfile only in request) + response = Response(resp.iter_content(chunk_size=8192), status=resp.status_code) # Copy headers from Jellyfin response excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']