Service API¶
Auto-generated reference for the Service base class that every
service plugin subclasses. See Creating a Service for a guided
walkthrough and Service Architecture for the wider object model.
TimeoutSession
¶
Bases: Session
requests.Session applying DEFAULT_TIMEOUT when the caller passes none.
requests has no native default timeout. Without one, a stalled connect or
read hangs forever. RnetSession bounds every request through its client's
connect_timeout/read_timeout, so this mirrors that on the requests path.
A per-request non-None timeout= wins. :class:TimeoutHTTPAdapter on the
mounted adapters still replaces an explicit timeout=None with the default,
so there is no unbounded read, as with RnetSession where the client-level
timeouts always apply. Pass a large timeout instead.
TimeoutHTTPAdapter
¶
Bases: HTTPAdapter
HTTPAdapter applying DEFAULT_TIMEOUT when the caller passes none.
Backstops :class:TimeoutSession for the session.send(prepared) path,
which bypasses Session.request. RnetSession bounds those too through its
client, so this keeps parity. A per-request non-None timeout= wins.
None (unset, or explicitly passed) gets the default, because the adapter
cannot distinguish the two, and rnet has no unbounded mode either.
Source code in unshackle/core/service.py
TrackRequest
dataclass
¶
Holds what the user requested for video codec and range selection.
Services read from this instead of ctx.parent.params for vcodec/range.
Attributes:
| Name | Type | Description |
|---|---|---|
codecs |
list[Codec]
|
Requested codecs from CLI. Empty list means no filter (accept any). |
ranges |
list[Range]
|
Requested ranges from CLI. Defaults to [SDR]. |
Service
¶
The Service Base Class.
A Service must define the abstract methods. The rest are optional overrides that fall back to the base implementation when a Service does not define them. The main flow operates the HTTP session and authentication methods first, then titles, then tracks and chapters. The license callbacks operate later still, during track download.
Source code in unshackle/core/service.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | |
get_tracks_for_variants
¶
Call fetch_fn for each codec/range combo in track_request, merge results.
Services that need separate API calls per codec/range combo can use this helper from their get_tracks() implementation.
The fetch_fn signature should be: (title, codec, range_) -> Tracks
For HYBRID range, this helper calls fetch_fn with HDR10 and DV separately, then merges the DV video tracks into the HDR10 result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
Title_T
|
The title to process. |
required |
fetch_fn
|
Callable[..., Tracks]
|
A callable that fetches tracks for a specific codec/range. |
required |
Source code in unshackle/core/service.py
get_session
staticmethod
¶
Creates a Python-requests HTTP session, adds common headers from config, cookies, retry handler, and a proxy if available. :returns: Prepared Python-requests HTTP session
Source code in unshackle/core/service.py
authenticate
¶
Authenticate the Service with Cookies and/or Credentials (Email/Username and Password).
This is effectively a login() function. Any API calls or object initializations needing to be made, should be made here. unshackle operates this method before any of the following abstract functions.
You should avoid storing or using the Credential outside this function. Make any calls you need for any Cookies, Tokens, or such, then use those.
Do not store the cookies outside this function either. However, you can load the cookies into the service HTTP session.
Source code in unshackle/core/service.py
request_input
¶
Request interactive input from the user.
When running locally (CLI), prompts through the shared rich console so the
prompt renders correctly alongside Live progress / log handlers.
When running in serve mode with an :class:InputBridge attached,
delegates to the bridge which relays the prompt to the remote client.
Source code in unshackle/core/service.py
search
¶
Find titles from the Service by query.
The Service class must take the query as a CLI argument. Ideally re-use the title ID argument (that is, self.title).
unshackle displays the search results in the order yielded.
Source code in unshackle/core/service.py
get_widevine_service_certificate
¶
Get the Widevine Service Certificate used for Privacy Mode.
:param challenge: The service challenge, providing this to a License endpoint should return the
privacy certificate that the service uses.
:param title: The current Title from get_titles that unshackle processes now. unshackle
gives this in case it holds data you need, for example for an HTTP request.
:param track: The current Track needing decryption. Provided for same reason as title.
:return: The Service Privacy Certificate as Bytes or a Base64 string. Do not Base64 Encode or
Decode the data, return as is to reduce unnecessary computations.
Source code in unshackle/core/service.py
get_widevine_license
¶
Get a Widevine License message by sending a License Request (challenge).
This License message contains the encrypted content keys and will be read by the Cdm and decrypted.
This is a very important request to get correct. A bad, unexpected, or missing value in the request can cause the service to detect your CDM device. The service can then ban, revoke, disable, or downgrade that device.
:param challenge: The license challenge from the Widevine CDM.
:param title: The current Title from get_titles that unshackle processes now. unshackle
gives this in case it holds data you need, for example for an HTTP request.
:param track: The current Track needing decryption. Provided for same reason as title.
:return: The License response as Bytes or a Base64 string. Do not Base64 Encode or
Decode the data, return as is to reduce unnecessary computations.
Source code in unshackle/core/service.py
get_playready_license
¶
Get a PlayReady License message by sending a License Request (challenge).
This License message contains the encrypted content keys and will be read by the CDM and decrypted.
This is a very important request to get correct. A bad, unexpected, or missing value in the request can cause the service to detect your CDM device. The service can then ban, revoke, disable, or downgrade that device.
:param challenge: The license challenge from the PlayReady CDM.
:param title: The current Title from get_titles that unshackle processes now. unshackle
gives this in case it holds data you need, for example for an HTTP request.
:param track: The current Track needing decryption. Provided for same reason as title.
:return: The License response as Bytes or a Base64 string. Do not Base64 Encode or
Decode the data, return as is to reduce unnecessary computations.
Source code in unshackle/core/service.py
get_clearkey_license
¶
Get a W3C ClearKey License (JWK Set) by sending a License Request (challenge).
Used for DASH org.w3.clearkey tracks. unshackle uses no CDM here: the challenge is
the W3C EME JSON license request, e.g. {"kids": ["<base64url>"], "type": "temporary"},
and the license is a JWK Set, e.g. {"keys": [{"kty": "oct", "k": "...", "kid": "..."}]}.
:param challenge: The JSON license request bytes to POST to the license server.
:param title: The current Title from get_titles that unshackle processes now. unshackle
gives this in case it holds data you need, for example for an HTTP request.
:param track: The current Track needing decryption. Provided for same reason as title.
:return: The JWK Set license as a dict, JSON str, or raw bytes. Return None (the default)
to let the framework POST the challenge to the manifest-provided Laurl, if any.
Services with no license server can instead pre-populate the DRM object's
content_keys in get_tracks.
Source code in unshackle/core/service.py
get_titles
abstractmethod
¶
Get Titles for the provided title ID.
Return a Movies, Series, or Album objects containing Movie, Episode, or Song title objects respectively. The returned data must be for the given title ID, or a spawn of the title ID.
You must return at least one object. If you do not, unshackle presumes an invalid Title ID.
You can use the data dictionary class instance attribute of each Title to store data you may need later on.
This can be useful to store information on each title that you need later, like any sub-asset IDs, or such.
Source code in unshackle/core/service.py
get_titles_cached
¶
Cached wrapper around get_titles() to reduce redundant API calls.
This method checks the cache before calling get_titles() and handles fallback to cached data when API calls fail.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title_id
|
str
|
Optional title ID for cache key generation. If not provided, will try to extract from service instance. |
None
|
Returns:
| Type | Description |
|---|---|
Titles_T
|
Titles object (Movies, Series, or Album) |
Source code in unshackle/core/service.py
apply_title_map
¶
Rewrite service-provided titles using the per-service title_map config.
title_map lives under services.<TAG> in unshackle.yaml. Applied after the
title cache so config edits take effect without a cache reset, and before any
--enrich override so enrich wins. See remap_titles for the match rules.
Source code in unshackle/core/service.py
get_tracks
abstractmethod
¶
Get Track objects of the Title.
Return a Tracks object, which itself can contain Video, Audio, Subtitle or even Chapters. Tracks.videos, Tracks.audio, Tracks.subtitles, and Track.chapters should be a List of Track objects.
Each Track in the Tracks should represent a Video/Audio track, Representation, or Adaptation, or a Subtitle file.
While one Track should only hold information for one downloadable track, try to get as many unique Track objects per track type so track selection by the root code can give you more options in terms of Resolution, Bitrate, Codecs, Language, and such.
No decision making or filtering of which Tracks get returned should happen here. It can be considered an error to filter for e.g. resolution, codec, and such. All filtering based on arguments will be done by the root code automatically when needed.
Make sure you correctly mark which Tracks have encryption, and which DRM System they
use, with the drm property.
If you can get the Track's KID (Key ID) as a 32 char (16 bit) HEX string, give it to the
Track's kid variable, as it will speed up the decryption process later on. The service
decides whether it is necessary. Generally if you can give it, without downloading any of
the Track's media data, then do.
:param title: The current Title from get_titles that unshackle processes now.
:return: Tracks object containing Video, Audio, Subtitles, and Chapters, if available.
Source code in unshackle/core/service.py
get_chapters
abstractmethod
¶
Get Chapters for the Title.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
Title_T
|
The current Title from |
required |
You must return a Chapters object containing 0 or more Chapter objects.
You do not need to set a Chapter number or sort/order the chapters in any way as the Chapters class automatically handles all of that for you. If there is no descriptive name for a Chapter then do not set a name at all.
You must not set Chapter names to "Chapter {n}" or such. If you (or the user)
wants "Chapter {n}" style Chapter names (or similar) then they can use the config
option chapter_fallback_name. For example, "Chapter {i:02}" for "Chapter 01".
Source code in unshackle/core/service.py
on_segment_downloaded
¶
Called when one of a Track's Segments has finished downloading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track
|
AnyTrack
|
The Track object that had a Segment downloaded. |
required |
segment
|
Path
|
The Path to the downloaded Segment. |
required |
Source code in unshackle/core/service.py
on_track_downloaded
¶
Called when a Track has finished downloading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track
|
AnyTrack
|
The downloaded Track object. |
required |
on_track_decrypted
¶
Called when a Track has finished decrypting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track
|
AnyTrack
|
The decrypted Track object. |
required |
drm
|
DRM_T
|
The DRM object it decrypted with. |
required |
segment
|
Optional[Segment]
|
The decrypted HLS segment information. |
None
|
Source code in unshackle/core/service.py
on_track_repacked
¶
Called when a Track has finished repacking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track
|
AnyTrack
|
The repacked Track object. |
required |
on_track_multiplex
¶
Called immediately before unshackle multiplexes a Track into a Container.
Note: Right now unshackle multiplexes only MKV containers. In the future unshackle can also call this when it multiplexes to other containers like MP4 with FFmpeg/mp4box.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track
|
AnyTrack
|
The repacked Track object. |
required |
Source code in unshackle/core/service.py
grow_session_pool
¶
Grow a shared requests session's connection pool to size before track downloads start.
The worker threads of every track draw on this one pool, because the downloader never
remounts an HTTP session the caller passes in (see downloaders/requests.py). The pool must hold
downloads * workers connections, or threads queue for a slot instead of reading.
Call this before any download thread exists: a remount races with other threads that call
get_adapter. RnetSession does not block on its idle-pool cap, so this function skips it.
Source code in unshackle/core/service.py
sanitize_proxy_for_log
¶
Sanitise a proxy URI for logs by masking any embedded userinfo (username/password).
serve sends these log lines to the client of a remote session, so the mask is
unconditional and debug mode never lifts it. mask_host hides the hostname as
well, for a proxy that came from the user-supplied Basic proxy provider.