Drop null keys from every licence, server and export source, not only Widevine. dl now finds remote sessions and imports through ServerKeySource instead of private fields.
A manifest snapshot module now saves and restores DASH/ISM parser state for --remote and export/import, so an import keeps the service's period filter without fetching the manifest.
cast_dl_params feeds ctx.params to dl() and dl.result() as on the CLI. A string lang value now returns 400 instead of splitting into letters, and -r accepts HDR10+.
Swagger now owns route registration instead of adding a second dead copy of every route; add json_body, enqueue_job, serialize_tracks and client_credential, build the serve middleware list once, and scan the users once per request.
Run session-tracks serialization, request cache IO, session cache dir removal (now outside the store lock), history reads and gzip over 1 MiB on worker threads; cache the per-key PBKDF2 namespace and key_id.
Session update events carry the new action as action_entry instead of the full log; access lines stay out of the log ring unless --debug. Job events build and redact their dict once, the progress interval matches the 0.5 s poll, and dashboard stats are serialized once.
Cache proxy provider catalogues (10 min), reuse vaults and CDMs across one batch licence call, and cache the health update check, /api/services entries and parsed service config.yaml.
A bad value now gets 400 INVALID_PARAMETERS at submit and the worker casts each option; a flat dl key such as quality or lang no longer fills a service option of the same name.
A service option named like a dl option (e.g. --profile) is no longer dropped or mixed with the dl value; the server casts nested values and keeps cli defaults nested.
server_cdm: true on a remote_services entry now licenses remote runs with the server CDM even when the service has its own cdm entry, which stays the local-run device and the fallback. A services.<TAG>.server_cdm replaces the server value for one service, per-service entries match the tag in any case, and the configured fallback logs at info.
BREAKING CHANGE: with server_cdm: true, a per-service cdm entry no longer makes your own device license remote sessions; use dl --cdm or services.<TAG>.server_cdm: false.
DASH no longer drops service-set DRM when the manifest has none, and the WebVTT rebase allows a cue that starts one segment early. --remote also sends single-track manifests matched by track id.
A VPN's synthetic DNS answer (fake-IP 198.18.0.0/15, 192.0.0.69) now works for a host name. An IP from the server, in any numeric form, must be public; dials to the client itself are closed.
A TUN-mode VPN with fake-IP DNS answers names from 198.18.0.0/15 or 2001:2::/48, which the LAN guard refused, so every dial failed. Refused hosts now warn once.
Judge a vault KID only on a file whose KID map holds it, tell remote vaults of a bad pair only once another key replaces it, and let the server CDM unflag a pair it returns; the server never flags a CDM-licensed pair. Log FFmpeg stderr on a failed check and name the missing CDM when none is set.
The client set server cookies with no domain and sent all of them to every host, so a CDN refused segment requests with 400 header too large. rnet sessions now send cookies too.
POST/DELETE /api/dashboard/drain refuses new sessions and jobs (SERVER_DRAINING, 409) and warns open sessions. A CDN can replace a 5xx body, so server errors are now 422, retryable ones and the tunnel check 424, and a new middleware turns any 5xx into 422.
BREAKING CHANGE: INTERNAL_ERROR, DOWNLOAD_ERROR, WORKER_ERROR and the fallback return 422 instead of 500; NETWORK_ERROR and SERVICE_UNAVAILABLE return 424 instead of 503; /api/tunnel/check failures return 424 instead of 502.
A reverse tunnel over the remote connection: the server's service traffic leaves through the client's own network. Only the client dials. Adds the tunnel-test command.
Every structured error carries a short ref, appended to its message and logged with the request; /api/dashboard/status gains per-service error counts. Gzip now compresses in place.
Cloudflare replaces an origin 502 body with its own page, so clients got "502: Bad gateway" in place of the error and resent each failed licence.
BREAKING CHANGE: SERVICE_ERROR and DRM_ERROR now return HTTP 422 instead of 502.
The server and the client both check a forwarded cache file before they write it, because Cacher decodes it with jsonpickle. The session-create cache write now runs off the event loop.
Click picks the subcommand before the dl callback applies the service config, so Services.resolve_command now applies remote and server first. A REST API job routes from its request only.
--proxy now overrides HTTP(S)_PROXY and the Windows system proxy while keeping the env CA bundle. Core fallback sessions use the run's proxy, and a warn-only audit hook names service code that sends a request or opens a raw socket outside the proxy; a session with proxy_exempt = True opts out.
The server CDM stub names only the manifest's KIDs; sibling keys stay content keys. API and HTTP vaults retry a 429, and a vault that is down or still rate limited costs one timeout per run.
Test servers shut down without the 0.5s poll wait, unit tests refuse non-loopback lookups, and three load-sensitive tests are steadied. Pushes to dev test Python 3.11 only; the docs job builds with uv.
The remote-dl client sends exact_lang in dl_params, and REST download jobs set it in the service ctx, so a service can skip the manifests of close-match languages.
An EXT-X-KEY whose Widevine PSSH holds a content id and no key id raised KIDNotFound. Covers download, licence-only, --all-drm, session keys, import and the server CDM path. Fixes#166.
A service that sets the flag gets no cookie load, no authenticate() call and no cookie save on a search, on the CLI and in the REST search handler. /api/services reports it as anonymous_search.