mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
docs: phase 2 of the prose style across docs and docstrings
This commit is contained in:
@@ -100,8 +100,8 @@ Declared at class level to configure framework behaviour:
|
||||
| `VAULT_TAG` | `Optional[str]` | Overrides the key-vault namespace so sibling services can share one vault. Default `None` (use the service's own tag). |
|
||||
| `AUTH_METHODS` | `Optional[tuple[str, ...]]` | Auth methods accepted (`"cookies"` / `"credentials"`). When `None`, the REST `/services` endpoint infers them from `authenticate()`. |
|
||||
| `NO_SUBTITLES` | `bool` | Set `True` on a service with no subtitle tracks to skip subtitle handling entirely. |
|
||||
| `ANIME` | `bool` | Set `True` when the catalog is anime, so metadata lookups prefer AniList. A title's own `anime` flag overrides it. |
|
||||
| `DAILY` | `bool` | Set `True` when the catalog is daily/date-based (talk shows, news, sports), so episodes are named by air date. A title's own `daily` flag overrides it. |
|
||||
| `ANIME` | `bool` | Set `True` when the catalogue is anime, so metadata lookups prefer AniList. A title's own `anime` flag overrides it. |
|
||||
| `DAILY` | `bool` | Set `True` when the catalogue is daily/date-based (talk shows, news, sports), so episodes are named by air date. A title's own `daily` flag overrides it. |
|
||||
|
||||
!!! note "`NO_SUBTITLES` is a convention, not a base-class attribute"
|
||||
`dl.py` uses `hasattr` to find `NO_SUBTITLES`, and the `Service` base class
|
||||
@@ -130,7 +130,7 @@ will use throughout the service:
|
||||
| `self.config` | This service's `config.yaml` contents (a dict), or `None` if absent. |
|
||||
| `self.log` | A `logging.Logger` named after your class. |
|
||||
| `self.session` | A prepared `requests.Session` with config headers and a retry adapter (5 retries, backoff 0.2, retries on 429/500/502/503/504). |
|
||||
| `self.cache` | A `Cacher` for arbitrary key/value data (tokens, etc.). |
|
||||
| `self.cache` | A `Cacher` for arbitrary key/value data, for example authentication tokens. |
|
||||
| `self.title_cache` | A region/account-aware `TitleCacher` used by `get_titles_cached`. |
|
||||
| `self.cache_dir` | `config.directories.cache / <ClassName>`. |
|
||||
| `self.track_request` | A `TrackRequest` built from the CLI `--vcodec` / `--range` / `--best-available` flags. |
|
||||
|
||||
@@ -171,7 +171,7 @@ Find titles in a service by query string. The service must have a `search()` met
|
||||
| `400` | `INVALID_PARAMETERS` | Missing `query`. |
|
||||
| `400` | `INVALID_SERVICE` | Unknown or disallowed service. |
|
||||
| `400` | `INVALID_PROXY` | Proxy could not be resolved. |
|
||||
| `502` | `SERVICE_ERROR` | Service failed to initialize, or does not support search. |
|
||||
| `502` | `SERVICE_ERROR` | Service failed to initialise, or does not support search. |
|
||||
|
||||
### `POST /api/list-titles`
|
||||
|
||||
@@ -235,7 +235,7 @@ These keys are sent only when the title carries them, so a title without them se
|
||||
| Key | Type | On | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `part` | integer | episode | Part index of a [split episode](../creating-a-service.md#split-episodes), counting from 1. |
|
||||
| `air_date` | string | episode | ISO air date of [dated content](../../guide/downloading.md#daily-and-date-based-content). unshackle names the episode by date instead of `SxxExx`. |
|
||||
| `air_date` | string | episode | ISO air date of [daily and date-based content](../../guide/downloading.md#daily-and-date-based-content). unshackle names the episode by date instead of `SxxExx`. |
|
||||
| `absolute` | integer | episode | Absolute episode number across all seasons. |
|
||||
| `daily` | boolean | episode | The episode is daily/date-based. Set from the title, or from the service's `DAILY` class attribute. |
|
||||
| `anime` | boolean | episode, movie | The title is anime, so metadata lookups prefer AniList. Set from the title, or from the service's `ANIME` class attribute. |
|
||||
@@ -268,7 +268,7 @@ Show the video, audio, and subtitle tracks for a title. For series, you can scop
|
||||
| --- | --- | --- | --- |
|
||||
| `service` | string | yes | Service tag. |
|
||||
| `title_id` | string | yes | Title identifier. |
|
||||
| `wanted` | string / string[] | no | Episode/season range (e.g. `"S01E01-S01E03"`), or a music track selector (e.g. `"1-5"`, `"1,3,7"`, or `"2x3"` for disc 2 track 3). A list of selectors is accepted too. |
|
||||
| `wanted` | string / string[] | no | Episode/season range (e.g. `"S01E01-S01E03"`), or a song selector (e.g. `"1-5"`, `"1,3,7"`, or `"2x3"` for disc 2 track 3). A list of selectors is accepted too. |
|
||||
| `season` | int/string | no | Season number (combined with `episode`). |
|
||||
| `episode` | int/string | no | Episode number (combined with `season`). |
|
||||
| `part` | int/string | no | Part index of a split episode (combined with `season` and `episode`). |
|
||||
@@ -383,7 +383,7 @@ Make a download job. It requires `service` and `title_id`. Every other field is
|
||||
| `vbitrate` / `abitrate` | int | `null` | Video / audio bitrate in kbps (positive). |
|
||||
| `vbitrate_range` / `abitrate_range` | string | `null` | `"MIN-MAX"` bitrate window. |
|
||||
| `range` | string[] | `["SDR"]` | Dynamic range(s). |
|
||||
| `channels` | number | `null` | Audio channel count. |
|
||||
| `channels` | number | `null` | Audio channel layout. |
|
||||
| `no_atmos` | boolean | `false` | Exclude Atmos tracks. |
|
||||
| `wanted` | string / string[] | `[]` | Episode/season selectors, as a list or as one comma-separated string. Accepts the part form, `"S01E01.2"`, and the air-date form, `"2026-08-11"` or `"2026-08-01:2026-08-31"`. For a music release, a selector is a track number, `"1-5"` or `"1,3,7"`, or `"{disc}x{track}"` such as `"2x3"`. |
|
||||
| `latest_episode` | boolean | `false` | Only the newest episode. |
|
||||
@@ -411,7 +411,7 @@ Make a download job. It requires `service` and `title_id`. Every other field is
|
||||
| `best_available` | boolean | `false` | Fall back to best available. |
|
||||
| `repack` | boolean | `false` | Add REPACK tag. |
|
||||
| `tag` | string | `null` | Release group tag. |
|
||||
| `tmdb_id` / `imdb_id` / `tvdb_id` / `anilist_id` | - | `null` | External ID overrides. Each resolves its metadata directly instead of by a title search, and is used for tagging. Set `enrich` to also take the title, year and original language. Give at most one of `tmdb_id`, `imdb_id` and `tvdb_id`, since unshackle resolves the others from it. Sending two returns `400`. `anilist_id` still combines with one of them. `tmdb_id` and `tvdb_id` must be positive integers, `imdb_id` must look like `tt1375666`, and `anilist_id` must be a positive integer or a string like `mal:12345`, or the request returns `400`. An ID whose provider is unconfigured, such as `tmdb_id` with no `tmdb_api_key`, fails the job rather than returning `400`. `anilist_id` needs no key. |
|
||||
| `tmdb_id` / `imdb_id` / `tvdb_id` / `anilist_id` | - | `null` | External ID overrides. Each resolves its metadata directly instead of by a title search, and is used for tagging. Set `enrich` to also take the title, year and original language. Give at most one of `tmdb_id`, `imdb_id` and `tvdb_id`, since unshackle resolves the others from it. Sending two returns `400`. `anilist_id` still combines with one of them. `tmdb_id` and `tvdb_id` must be positive integers, `imdb_id` must look like `tt1375666`, and `anilist_id` must be a positive integer or a string like `mal:12345`, or the request returns `400`. An ID whose provider is unconfigured, such as `tmdb_id` with no `tmdb_api_key`, fails the job rather than returning `400`. `anilist_id` needs no API key. |
|
||||
| `tvdb_order` | `official`, `dvd`, `absolute`, `alternate`, `regional` | `null` | Renumber episodes to a TVDB season order. Falls back to the `tvdb_order` config option. |
|
||||
| `enrich` | boolean | `false` | Overwrite title, year and original language with the external source's. Needs one of `tmdb_id`, `imdb_id`, `tvdb_id` or `anilist_id`. Without one the job fails instead of returning `400`. |
|
||||
| `daily` | boolean | `false` | Treat the title as daily/date-based content and fill missing episode air dates from TVDB. The fill needs `enrich` and a TVDB ID. An air date the service already set is kept. |
|
||||
@@ -527,9 +527,9 @@ The `data` field of every event is the same job object that `GET /api/download/j
|
||||
| `snapshot` | Immediately, as the first event. Gives the job's current state. |
|
||||
| `status` | The job leaves the queue and starts to download. |
|
||||
| `progress` | The worker's progress record changed. The server reads that record every 0.5 seconds, so you get at most two of these events each second, and none while the record stays the same. |
|
||||
| `completed` | The job finished. The server then closes the stream. |
|
||||
| `failed` | The job failed. The server then closes the stream. |
|
||||
| `cancelled` | The job was cancelled. The server then closes the stream. |
|
||||
| `completed` | The job finished. The server then closes the event stream. |
|
||||
| `failed` | The job failed. The server then closes the event stream. |
|
||||
| `cancelled` | The job was cancelled. The server then closes the event stream. |
|
||||
|
||||
The server sends a `: keep-alive` comment each 15 seconds while the job is quiet, to keep proxies from closing an idle connection. Ignore these lines.
|
||||
|
||||
@@ -862,7 +862,7 @@ Poll for the authentication status and any pending interactive prompt (OTP, PIN,
|
||||
| Status | Error code | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `200` | - | Status returned. |
|
||||
| `403` | `FORBIDDEN` | Request IP differs from the session creator. |
|
||||
| `403` | `FORBIDDEN` | Request IP differs from the remote session creator. |
|
||||
| `404` | `SESSION_NOT_FOUND` | No such session. |
|
||||
|
||||
### `POST /api/session/{session_id}/prompt`
|
||||
@@ -986,7 +986,7 @@ Get the content keys for the DRM. The `mode` field selects one of two modes.
|
||||
| Status | Error code | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `200` | - | License/keys returned. |
|
||||
| `400` | `INVALID_INPUT` | Missing challenge/PSSH, or no CDM device configured for the key. |
|
||||
| `400` | `INVALID_INPUT` | Missing challenge/PSSH, or no CDM device configured for the API key. |
|
||||
| `400` | `INVALID_PARAMETERS` | Unsupported `drm_type`. |
|
||||
| `404` | `TRACK_NOT_FOUND` | Unknown track ID. |
|
||||
| `404` | `NO_CONTENT` | Server CDM produced no keys. |
|
||||
|
||||
+11
-11
@@ -99,13 +99,13 @@ but the defaults hold for the overwhelming majority of responses.
|
||||
| `INVALID_PROXY` | 400 | The supplied proxy specification could not be resolved or is malformed. |
|
||||
| `INVALID_PARAMETERS` | 400 | One or more download or query parameters failed validation (bad codec, bitrate, sort field, and so on). |
|
||||
| `AUTH_FAILED` | 401 | Authentication with the streaming service failed (bad credentials or cookies). |
|
||||
| `FORBIDDEN` | 403 | The action is not allowed. Raised by server-side gates (per-key restrictions on CDM or credential overrides) and by session IP binding. |
|
||||
| `GEOFENCE` | 403 | The content is not available in the applicable region. |
|
||||
| `FORBIDDEN` | 403 | The action is not allowed. Raised by server-side gates (per-key restrictions on CDM or credential overrides) and by remote session IP binding. |
|
||||
| `GEOFENCE` | 403 | The title is not available in the applicable region. |
|
||||
| `NOT_FOUND` | 404 | A requested resource (title, history entry, and so on) does not exist. |
|
||||
| `NO_CONTENT` | 404 | The request was valid but produced nothing: no matching titles, tracks, episodes, or keys. |
|
||||
| `JOB_NOT_FOUND` | 404 | The referenced download job does not exist. |
|
||||
| `SESSION_NOT_FOUND` | 404 | The remote-dl session does not exist or has expired. |
|
||||
| `TRACK_NOT_FOUND` | 404 | A referenced track ID is not present in the session. |
|
||||
| `TRACK_NOT_FOUND` | 404 | A referenced track ID is not present in the remote session. |
|
||||
| `CONFLICT` | 409 | The target resource is in a state that disallows the action (retrying a running job, prioritising a non-queued job, clearing cache during an active download). |
|
||||
| `RATE_LIMITED` | 429 | The streaming service is rate-limiting requests. Retryable. |
|
||||
|
||||
@@ -141,14 +141,14 @@ The classification is keyword-based, checked in order:
|
||||
|
||||
| If the error mentions... | It becomes | Retryable |
|
||||
|---|---|---|
|
||||
| auth, login, credential, unauthorized, forbidden, token | `AUTH_FAILED` (401) | no |
|
||||
| connection, timeout, network, unreachable, socket, dns, resolve (or `ConnectionError`, `TimeoutError`, `URLError`, `SSLError`) | `NETWORK_ERROR` (503) | yes |
|
||||
| geofence, region, "not available in", territory | `GEOFENCE` (403) | no |
|
||||
| "not found", 404, "does not exist", "invalid id" | `NOT_FOUND` (404) | no |
|
||||
| rate limit, too many requests, 429, throttle | `RATE_LIMITED` (429) | yes |
|
||||
| drm, license, widevine, playready, decrypt | `DRM_ERROR` (502) | no |
|
||||
| "service unavailable", 503, maintenance, "temporarily unavailable" | `SERVICE_UNAVAILABLE` (503) | yes |
|
||||
| invalid, malformed, validation (or `ValueError`, `ValidationError`) | `INVALID_INPUT` (400) | no |
|
||||
| `auth`, `login`, `credential`, `unauthorized`, `forbidden`, `token` | `AUTH_FAILED` (401) | no |
|
||||
| `connection`, `timeout`, `network`, `unreachable`, `socket`, `dns`, `resolve` (or `ConnectionError`, `TimeoutError`, `URLError`, `SSLError`) | `NETWORK_ERROR` (503) | yes |
|
||||
| `geofence`, `region`, `not available in`, `territory` | `GEOFENCE` (403) | no |
|
||||
| `not found`, `404`, `does not exist`, `invalid id` | `NOT_FOUND` (404) | no |
|
||||
| `rate limit`, `too many requests`, `429`, `throttle` | `RATE_LIMITED` (429) | yes |
|
||||
| `drm`, `license`, `widevine`, `playready`, `decrypt` | `DRM_ERROR` (502) | no |
|
||||
| `service unavailable`, `503`, `maintenance`, `temporarily unavailable` | `SERVICE_UNAVAILABLE` (503) | yes |
|
||||
| `invalid`, `malformed`, `validation` (or `ValueError`, `ValidationError`) | `INVALID_INPUT` (400) | no |
|
||||
| anything else | `INTERNAL_ERROR` (500) | no |
|
||||
|
||||
Categorised errors include a `reason` tag inside `details` (such as
|
||||
|
||||
@@ -148,7 +148,7 @@ request and response in full.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/health` | Liveness and version check. Never requires a key. |
|
||||
| `GET` | `/api/health` | Liveness and version check. Never requires an API key. |
|
||||
| `GET` | `/api/services` | List the services available on this server. |
|
||||
| `POST` | `/api/search` | Run a service's search for a query. |
|
||||
| `POST` | `/api/list-titles` | List the titles behind a service title ID. |
|
||||
@@ -190,7 +190,7 @@ request and response in full.
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| `POST` | `/api/session/create` | Start a server-side, authenticated service session. |
|
||||
| `GET` | `/api/session/{session_id}` | Session info and time-to-live. |
|
||||
| `GET` | `/api/session/{session_id}` | Remote session info and time-to-live. |
|
||||
| `DELETE` | `/api/session/{session_id}` | Tear down a session. |
|
||||
| `GET` | `/api/session/{session_id}/titles` | Titles for the session's title ID. |
|
||||
| `POST` | `/api/session/{session_id}/tracks` | Tracks (with URLs) for a chosen title. |
|
||||
|
||||
@@ -268,7 +268,7 @@ The server mounts all these routes, even in `--remote-only` mode. Paths use the
|
||||
| `GET` | `/api/session/{id}/prompt` | Poll interactive auth status / pending prompt |
|
||||
| `POST` | `/api/session/{id}/prompt` | Submit an answer to a pending prompt |
|
||||
| `GET` | `/api/session/{id}` | Session info (validity, TTL, counts) |
|
||||
| `DELETE` | `/api/session/{id}` | Close the session, return updated cache, clean up |
|
||||
| `DELETE` | `/api/session/{id}` | Close the remote session, return updated cache, clean up |
|
||||
|
||||
### Create: request and response
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@ Declared at the top of your service class, these are static descriptors:
|
||||
| `GEOFENCE` | `tuple[str, ...]` | ISO region codes the service requires; empty means no geofence. The first entry is treated as the "main region" for automatic proxy selection. |
|
||||
| `VAULT_TAG` | `Optional[str]` | Overrides the key-vault namespace; defaults to the service's own tag. |
|
||||
| `AUTH_METHODS` | `Optional[tuple[str, ...]]` | Accepted auth methods (`"cookies"` / `"credentials"`). When `None`, the REST `/services` endpoint infers them from `authenticate()`. |
|
||||
| `ANIME` | `bool` | The catalog is anime, so metadata lookups prefer AniList. A title's own `anime` flag overrides it. |
|
||||
| `DAILY` | `bool` | The catalog is daily/date-based, so episodes are named by air date. A title's own `daily` flag overrides it. |
|
||||
| `ANIME` | `bool` | The catalogue is anime, so metadata lookups prefer AniList. A title's own `anime` flag overrides it. |
|
||||
| `DAILY` | `bool` | The catalogue is daily/date-based, so episodes are named by air date. A title's own `daily` flag overrides it. |
|
||||
|
||||
### Required methods
|
||||
|
||||
@@ -105,7 +105,7 @@ up the objects your methods rely on:
|
||||
|
||||
| Attribute | What it is |
|
||||
|---|---|
|
||||
| `self.config` | The per-service config slice (see [Request and session objects](#request-and-session-objects)). |
|
||||
| `self.config` | The per-service config slice (see [Request and HTTP session objects](#request-and-http-session-objects)). |
|
||||
| `self.log` | A `logging.Logger` named after your class. |
|
||||
| `self.session` | A ready-to-use HTTP session (headers, retries, and proxy applied). |
|
||||
| `self.cache` | A generic key/value `Cacher` namespaced to the service. |
|
||||
@@ -114,10 +114,11 @@ up the objects your methods rely on:
|
||||
| `self.credential` | The active `Credential`, or `None` until authenticated. |
|
||||
| `self.track_request` | A `TrackRequest` describing the requested codecs, ranges, and best-available flag (see below). |
|
||||
|
||||
The constructor also resolves proxies: it reads `--proxy` / `--proxy-query` /
|
||||
`--proxy-provider`, consults the per-service `proxy_map`, and, if `GEOFENCE` is set and
|
||||
you did not give an explicit proxy, does a live IP check and auto-fetches a proxy to
|
||||
`GEOFENCE[0]` when it detects a geoblock. See [Proxies and VPN](../guide/proxies-and-vpn.md).
|
||||
The constructor also resolves proxies: it reads `--proxy` and the `proxy_query` and
|
||||
`proxy_provider` context params that `dl` derives from it, consults the per-service
|
||||
`proxy_map`, and, if `GEOFENCE` is set and you did not give an explicit proxy, does a live
|
||||
IP check and auto-fetches a proxy to `GEOFENCE[0]` when it detects a geoblock.
|
||||
See [Proxies and VPN](../guide/proxies-and-vpn.md).
|
||||
|
||||
!!! note "`TrackRequest`"
|
||||
`self.track_request` is a small dataclass built from the CLI: `codecs`
|
||||
@@ -223,7 +224,7 @@ After the service fetches the titles (and after any cache), the framework applie
|
||||
**exact-string** rename (it compares `Episode`s on their show `title`, and `Movie`s and
|
||||
`Song`s on their `name`), so you can change a platform's odd naming to what your
|
||||
library expects. `get_titles_cached()` also cache-wraps the titles, and it is region-
|
||||
and account-aware. See the [TitleCacher](#request-and-session-objects) notes.
|
||||
and account-aware. See the [TitleCacher](#request-and-http-session-objects) notes.
|
||||
|
||||
The ordering here is deliberate on both sides. The framework applies `title_map` **after**
|
||||
the title cache so that editing the map takes effect immediately, without forcing a cache
|
||||
@@ -376,7 +377,7 @@ service to talk to the platform's license server. unshackle recognizes five DRM
|
||||
| `Widevine` | Widevine | CDM challenge → license server → content keys (`.wvd` device) |
|
||||
| `PlayReady` | PlayReady | CDM challenge (WRM header) → license server → content keys (`.prd` device) |
|
||||
| `ClearKeyCENC` | W3C `org.w3.clearkey` over CENC | JSON KID request → JWK set; no CDM |
|
||||
| `ClearKey` | HLS AES-128 | key fetched directly from a URI; no CDM, no challenge |
|
||||
| `ClearKey` | HLS AES-128 | content key fetched directly from a URI; no CDM, no challenge |
|
||||
| `MonaLisa` | Proprietary WASM DRM | keys extracted locally; no license server |
|
||||
|
||||
!!! warning "`ClearKey` vs `ClearKeyCENC`"
|
||||
@@ -422,7 +423,7 @@ extracts the RPU with `dovi_tool`, and injects it onto the base layer, to make a
|
||||
track that carries both HDR10 metadata and the DV RPU. Both are automatic once the
|
||||
pipeline selects the right tracks. A service only needs to give accurate ranges.
|
||||
|
||||
## Request and session objects
|
||||
## Request and HTTP session objects
|
||||
|
||||
Everything a service does over the network flows through objects the base constructor
|
||||
prepared, all derived from the Click context (`ctx`) that `dl` passes in.
|
||||
|
||||
Reference in New Issue
Block a user