From 919eeac79da236a99a709d52e6e229cd9c32f3d7 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Wed, 16 Sep 2026 09:03:38 +0200 Subject: [PATCH] avoid 400 error on wv --- routes/streams/__init__.py | 112 +++++++++++++++++-------------------- service.py | 5 ++ 2 files changed, 55 insertions(+), 62 deletions(-) diff --git a/routes/streams/__init__.py b/routes/streams/__init__.py index b508085..6af7b95 100644 --- a/routes/streams/__init__.py +++ b/routes/streams/__init__.py @@ -724,40 +724,31 @@ def make_helpers(manager, service): return redirect(manifest_url) else: - # Encrypted but not ClearKey (e.g. Widevine-only catchup). - if receiver_side: - # Client handles its own DRM — same proxy-if-needed/redirect - # passthrough as the unencrypted case above. - if manager.needs_proxy(provider): - return service.get_proxied_catchup_manifest( - provider, content_id, start_time, end_time, epg_id, country - ) - manifest_url = manager.get_catchup_manifest( - provider_name=provider, - channel_id=content_id, - start_time=start_time, - end_time=end_time, - epg_id=epg_id, - country=country, - drm_variant=drm_variant, + # Encrypted, not ClearKey (Widevine / PlayReady / other). + # Same reasoning as the live path: the server cannot decrypt + # these schemes, receiver_side carries no information, and the + # client negotiates the license via the x-kodi-drm-configs + # header. Both receiver_side values take the same path. + if manager.needs_proxy(provider): + return service.get_proxied_catchup_manifest( + provider, content_id, start_time, end_time, epg_id, country ) - if not manifest_url: - response.status = 404 - return { - "error": f'Catchup manifest not available for channel "{content_id}"' - } - return redirect(manifest_url) - else: - # Server can't decrypt non-ClearKey schemes — honest error - # rather than silently proxying content the client likely - # can't play anyway. - response.status = 400 + manifest_url = manager.get_catchup_manifest( + provider_name=provider, + channel_id=content_id, + start_time=start_time, + end_time=end_time, + epg_id=epg_id, + country=country, + drm_variant=drm_variant, + ) + if not manifest_url: + response.status = 404 return { - "error": ( - f'Catchup content for channel "{content_id}" does not support ' - f"decrypted playback (requires ClearKey or unencrypted)" - ) + "error": f'Catchup manifest not available for channel "{content_id}"' } + logger.debug(f"Redirecting to catchup manifest: {manifest_url}") + return redirect(manifest_url) # ================================================================== # Live / event / vod / recording path @@ -826,38 +817,35 @@ def make_helpers(manager, service): return _redirect_or_fetch(content_type, provider, content_id, country, drm_variant) else: - # Encrypted but not ClearKey (e.g. Widevine-only). - if receiver_side: - # When the caller explicitly requested software DRM and we found - # no ClearKey keys, surface a clear error rather than silently - # serving a Widevine stream the client cannot decrypt. - if drm_variant == "software": - logger.warning( - f"Software DRM requested but no ClearKey keys found for " - f"{provider}/{content_id}" - ) - response.status = 400 - return {"error": "Software DRM not available for this content"} - - # Client handles its own DRM — same proxy-if-needed/redirect - # passthrough as the unencrypted case above. - if manager.needs_proxy(provider): - return service.get_proxied_manifest( - provider, content_id, - highest_quality_only=highest_quality_only, - ) - return _redirect_or_fetch(content_type, provider, content_id, country, drm_variant) - else: - # Server can't decrypt non-ClearKey schemes — honest error - # rather than silently proxying content the client likely - # can't play anyway. + # Encrypted, not ClearKey (Widevine / PlayReady / other non-ClearKey). + # + # The server has no CDM and cannot decrypt these schemes, so + # client_drm / receiver_side carries no information here — there is + # no server-side option to prefer. The client consumes the DRM + # config from the x-kodi-drm-configs response header (populated by + # _build_drm_header above) and negotiates the license itself. + # Therefore both receiver_side values take the same path: hand the + # client a playable manifest, proxy if the provider needs it, + # redirect/fetch otherwise. + # + # The one exception is drm_variant=software combined with + # receiver_side=True: that combination explicitly asked for + # server-decryptable content, and none exists for this channel. + # An honest 400 is correct there. + if receiver_side and drm_variant == "software": + logger.warning( + f"Software DRM requested but no ClearKey keys found for " + f"{provider}/{content_id}" + ) response.status = 400 - return { - "error": ( - f'{content_type.capitalize()} "{content_id}" does not support ' - f"decrypted playback (requires ClearKey or unencrypted)" - ) - } + return {"error": "Software DRM not available for this content"} + + if manager.needs_proxy(provider): + return service.get_proxied_manifest( + provider, content_id, + highest_quality_only=highest_quality_only, + ) + return _redirect_or_fetch(content_type, provider, content_id, country, drm_variant) # Return all helpers as a dict for submodules to use return { diff --git a/service.py b/service.py index f7751a6..71ce159 100644 --- a/service.py +++ b/service.py @@ -769,6 +769,11 @@ class UltimateService: entry_content = "" catchup_window = getattr(channel, 'catchup_hours', 0) if include_catchup else 0 + # getattr's default only applies when the attribute is absent — Channel + # always defines catchup_hours, and providers that don't populate it + # leave it as None. Normalise None to 0 so the comparison below is + # type-safe. + catchup_window = catchup_window or 0 if catchup_window > 0: catchup_type = getattr(channel, 'catchup_type', 'append') catchup_source = getattr(channel, 'catchup_source', '?start_time={utc}&end_time={utcend}')