Files
unshackle/dev/service-architecture/index.html
T
2026-07-04 01:24:56 +00:00

2984 lines
90 KiB
HTML

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Movie, TV, and Music archival software — user guide and REST API reference">
<link rel="canonical" href="https://docs.unshackle.dev/dev/service-architecture/">
<link rel="prev" href="../creating-a-service/">
<link rel="next" href="../rest-api/">
<link rel="icon" href="../../assets/favicon.svg">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
<title>Service Architecture - unshackle</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../../assets/_mkdocstrings.css">
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#service-architecture" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="unshackle" class="md-header__button md-logo" aria-label="unshackle" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m800-274-80-80v-206H514l-80-80h166v-80q0-50-34.5-85T481-840q-50 0-84 34.5T363-720v9l-73-73q22-61 75-98.5T481-920q83 0 141 58.5T680-720v80h40q33 0 56.5 23.5T800-560v286Zm20 246-62-62q-11 5-20 7.5T720-80H240q-33 0-56.5-23.5T160-160v-400q0-25 14.5-46t37.5-30L28-820l56-56L876-84l-56 56ZM686-160 539-309q-11 11-25.5 17t-31.5 6q-33 0-56.5-23.5T402-366q0-17 6-31.5t17-25.5L286-560h-46v400h446ZM486-360Zm131-97Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
unshackle
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Service Architecture
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="(prefers-color-scheme)" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1M8 13h8v-2H8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4a5 5 0 0 0 5-5 5 5 0 0 0-5-5"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_2" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h10a5 5 0 0 0 5-5 5 5 0 0 0-5-5m0 8a3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3 3 3 0 0 1-3 3"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to system preference" type="radio" name="__palette" id="__palette_2">
<label class="md-header__button md-icon" title="Switch to system preference" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 7H7a5 5 0 0 0-5 5 5 5 0 0 0 5 5h10a5 5 0 0 0 5-5 5 5 0 0 0-5-5M7 15a3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3 3 3 0 0 1-3 3"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/unshackle-dl/unshackle" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
unshackle-dl/unshackle
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../../getting-started/installation/" class="md-tabs__link">
Getting Started
</a>
</li>
<li class="md-tabs__item">
<a href="../../guide/downloading/" class="md-tabs__link">
User Guide
</a>
</li>
<li class="md-tabs__item">
<a href="../../reference/configuration/" class="md-tabs__link">
Configuration
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="../creating-a-service/" class="md-tabs__link">
Developer
</a>
</li>
<li class="md-tabs__item">
<a href="../../reference/api/service/" class="md-tabs__link">
API Reference
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="unshackle" class="md-nav__button md-logo" aria-label="unshackle" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m800-274-80-80v-206H514l-80-80h166v-80q0-50-34.5-85T481-840q-50 0-84 34.5T363-720v9l-73-73q22-61 75-98.5T481-920q83 0 141 58.5T680-720v80h40q33 0 56.5 23.5T800-560v286Zm20 246-62-62q-11 5-20 7.5T720-80H240q-33 0-56.5-23.5T160-160v-400q0-25 14.5-46t37.5-30L28-820l56-56L876-84l-56 56ZM686-160 539-309q-11 11-25.5 17t-31.5 6q-33 0-56.5-23.5T402-366q0-17 6-31.5t17-25.5L286-560h-46v400h446ZM486-360Zm131-97Z"/></svg>
</a>
unshackle
</label>
<div class="md-nav__source">
<a href="https://github.com/unshackle-dl/unshackle" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
unshackle-dl/unshackle
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Getting Started
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Getting Started
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../getting-started/installation/" class="md-nav__link">
<span class="md-ellipsis">
Installation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../getting-started/quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../getting-started/configuration-file/" class="md-nav__link">
<span class="md-ellipsis">
The Configuration File
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" >
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
<span class="md-ellipsis">
User Guide
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
User Guide
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../guide/downloading/" class="md-nav__link">
<span class="md-ellipsis">
Downloading
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/cli-reference/" class="md-nav__link">
<span class="md-ellipsis">
CLI Reference
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/drm-and-cdm/" class="md-nav__link">
<span class="md-ellipsis">
DRM & CDM Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/proxies-and-vpn/" class="md-nav__link">
<span class="md-ellipsis">
Proxies & VPN
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/vaults/" class="md-nav__link">
<span class="md-ellipsis">
Key Vaults
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/output-and-naming/" class="md-nav__link">
<span class="md-ellipsis">
Output & Naming
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/subtitles/" class="md-nav__link">
<span class="md-ellipsis">
Subtitles
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../guide/troubleshooting/" class="md-nav__link">
<span class="md-ellipsis">
Troubleshooting
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" >
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="0">
<span class="md-ellipsis">
Configuration
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4">
<span class="md-nav__icon md-icon"></span>
Configuration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../reference/configuration/" class="md-nav__link">
<span class="md-ellipsis">
Reference
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" checked>
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Developer
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Developer
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../creating-a-service/" class="md-nav__link">
<span class="md-ellipsis">
Creating a Service
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Service Architecture
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Service Architecture
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#the-shape-of-a-download" class="md-nav__link">
<span class="md-ellipsis">
The shape of a download
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-service-base-class" class="md-nav__link">
<span class="md-ellipsis">
The Service base class
</span>
</a>
<nav class="md-nav" aria-label="The Service base class">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#class-variables" class="md-nav__link">
<span class="md-ellipsis">
Class variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#required-methods" class="md-nav__link">
<span class="md-ellipsis">
Required methods
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optional-methods-and-hooks" class="md-nav__link">
<span class="md-ellipsis">
Optional methods and hooks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-__init__-wires-up-for-you" class="md-nav__link">
<span class="md-ellipsis">
What __init__ wires up for you
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#titles-and-the-title-hierarchy" class="md-nav__link">
<span class="md-ellipsis">
Titles and the Title hierarchy
</span>
</a>
<nav class="md-nav" aria-label="Titles and the Title hierarchy">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#constructors-at-a-glance" class="md-nav__link">
<span class="md-ellipsis">
Constructors at a glance
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#title-remapping" class="md-nav__link">
<span class="md-ellipsis">
Title remapping
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#tracks-and-the-track-model" class="md-nav__link">
<span class="md-ellipsis">
Tracks and the Track model
</span>
</a>
<nav class="md-nav" aria-label="Tracks and the Track model">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-base-track" class="md-nav__link">
<span class="md-ellipsis">
The base Track
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#video" class="md-nav__link">
<span class="md-ellipsis">
Video
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#audio" class="md-nav__link">
<span class="md-ellipsis">
Audio
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#subtitle" class="md-nav__link">
<span class="md-ellipsis">
Subtitle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#chapters-and-attachments" class="md-nav__link">
<span class="md-ellipsis">
Chapters and attachments
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#selection-sorting-and-muxing" class="md-nav__link">
<span class="md-ellipsis">
Selection, sorting, and muxing
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#manifest-parsers" class="md-nav__link">
<span class="md-ellipsis">
Manifest parsers
</span>
</a>
<nav class="md-nav" aria-label="Manifest parsers">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-parser-contract" class="md-nav__link">
<span class="md-ellipsis">
The parser contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#segment-filtering-hooks" class="md-nav__link">
<span class="md-ellipsis">
Segment-filtering hooks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#format-gotchas-worth-knowing" class="md-nav__link">
<span class="md-ellipsis">
Format gotchas worth knowing
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#drm-hooks" class="md-nav__link">
<span class="md-ellipsis">
DRM hooks
</span>
</a>
<nav class="md-nav" aria-label="DRM hooks">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-service-side-hooks" class="md-nav__link">
<span class="md-ellipsis">
The service-side hooks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#how-a-cdm-is-chosen-and-keys-are-found" class="md-nav__link">
<span class="md-ellipsis">
How a CDM is chosen and keys are found
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#dolby-vision-and-hybrid" class="md-nav__link">
<span class="md-ellipsis">
Dolby Vision and hybrid
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#request-and-session-objects" class="md-nav__link">
<span class="md-ellipsis">
Request and session objects
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#where-to-go-next" class="md-nav__link">
<span class="md-ellipsis">
Where to go next
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_3" >
<div class="md-nav__link md-nav__container">
<a href="../rest-api/" class="md-nav__link ">
<span class="md-ellipsis">
REST API
</span>
</a>
<label class="md-nav__link " for="__nav_5_3" id="__nav_5_3_label" tabindex="">
<span class="md-nav__icon md-icon"></span>
</label>
</div>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_3">
<span class="md-nav__icon md-icon"></span>
REST API
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../rest-api/quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../rest-api/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../rest-api/endpoints/" class="md-nav__link">
<span class="md-ellipsis">
Endpoints
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../rest-api/job-lifecycle/" class="md-nav__link">
<span class="md-ellipsis">
Job Lifecycle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../rest-api/remote-sessions/" class="md-nav__link">
<span class="md-ellipsis">
Remote Sessions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../rest-api/errors/" class="md-nav__link">
<span class="md-ellipsis">
Error Responses
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_6" >
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="0">
<span class="md-ellipsis">
API Reference
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_6_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
API Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../reference/api/service/" class="md-nav__link">
<span class="md-ellipsis">
Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/config/" class="md-nav__link">
<span class="md-ellipsis">
Config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/tracks/" class="md-nav__link">
<span class="md-ellipsis">
Tracks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/titles/" class="md-nav__link">
<span class="md-ellipsis">
Titles
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../reference/api/drm/" class="md-nav__link">
<span class="md-ellipsis">
DRM
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#the-shape-of-a-download" class="md-nav__link">
<span class="md-ellipsis">
The shape of a download
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-service-base-class" class="md-nav__link">
<span class="md-ellipsis">
The Service base class
</span>
</a>
<nav class="md-nav" aria-label="The Service base class">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#class-variables" class="md-nav__link">
<span class="md-ellipsis">
Class variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#required-methods" class="md-nav__link">
<span class="md-ellipsis">
Required methods
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#optional-methods-and-hooks" class="md-nav__link">
<span class="md-ellipsis">
Optional methods and hooks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-__init__-wires-up-for-you" class="md-nav__link">
<span class="md-ellipsis">
What __init__ wires up for you
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#titles-and-the-title-hierarchy" class="md-nav__link">
<span class="md-ellipsis">
Titles and the Title hierarchy
</span>
</a>
<nav class="md-nav" aria-label="Titles and the Title hierarchy">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#constructors-at-a-glance" class="md-nav__link">
<span class="md-ellipsis">
Constructors at a glance
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#title-remapping" class="md-nav__link">
<span class="md-ellipsis">
Title remapping
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#tracks-and-the-track-model" class="md-nav__link">
<span class="md-ellipsis">
Tracks and the Track model
</span>
</a>
<nav class="md-nav" aria-label="Tracks and the Track model">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-base-track" class="md-nav__link">
<span class="md-ellipsis">
The base Track
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#video" class="md-nav__link">
<span class="md-ellipsis">
Video
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#audio" class="md-nav__link">
<span class="md-ellipsis">
Audio
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#subtitle" class="md-nav__link">
<span class="md-ellipsis">
Subtitle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#chapters-and-attachments" class="md-nav__link">
<span class="md-ellipsis">
Chapters and attachments
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#selection-sorting-and-muxing" class="md-nav__link">
<span class="md-ellipsis">
Selection, sorting, and muxing
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#manifest-parsers" class="md-nav__link">
<span class="md-ellipsis">
Manifest parsers
</span>
</a>
<nav class="md-nav" aria-label="Manifest parsers">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-parser-contract" class="md-nav__link">
<span class="md-ellipsis">
The parser contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#segment-filtering-hooks" class="md-nav__link">
<span class="md-ellipsis">
Segment-filtering hooks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#format-gotchas-worth-knowing" class="md-nav__link">
<span class="md-ellipsis">
Format gotchas worth knowing
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#drm-hooks" class="md-nav__link">
<span class="md-ellipsis">
DRM hooks
</span>
</a>
<nav class="md-nav" aria-label="DRM hooks">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#the-service-side-hooks" class="md-nav__link">
<span class="md-ellipsis">
The service-side hooks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#how-a-cdm-is-chosen-and-keys-are-found" class="md-nav__link">
<span class="md-ellipsis">
How a CDM is chosen and keys are found
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#dolby-vision-and-hybrid" class="md-nav__link">
<span class="md-ellipsis">
Dolby Vision and hybrid
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#request-and-session-objects" class="md-nav__link">
<span class="md-ellipsis">
Request and session objects
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#where-to-go-next" class="md-nav__link">
<span class="md-ellipsis">
Where to go next
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="service-architecture">Service Architecture<a class="headerlink" href="#service-architecture" title="Permanent link">&para;</a></h1>
<p>This page is the conceptual map of the internal object model that a <strong>service author</strong>
works against. It ties together the moving parts: the <code>Service</code> base class, the
<code>Title</code> hierarchy a service returns, the <code>Track</code> model those titles carry, the
manifest parsers that produce tracks, the DRM hooks that license and decrypt them,
and the request/session objects wired up for you at construction time.</p>
<div class="admonition info">
<p class="admonition-title">Who this page is for</p>
<p>This is a <strong>developer reference</strong>. If you only want to <em>run</em> downloads, you never
touch these classes directly; see <a href="../../guide/downloading/">Downloading</a> and the
<a href="../../guide/cli-reference/">CLI reference</a> instead. If you are writing or reading a
service plugin, this page explains what each object is and how the pieces connect.
For a guided, step-by-step build, see <a href="../creating-a-service/">Creating a Service</a>;
for exact signatures, see the auto-generated <a href="../../reference/api/service/">Service API</a>,
<a href="../../reference/api/titles/">Titles API</a>, and <a href="../../reference/api/tracks/">Tracks API</a>.</p>
</div>
<h2 id="the-shape-of-a-download">The shape of a download<a class="headerlink" href="#the-shape-of-a-download" title="Permanent link">&para;</a></h2>
<p>A service is a translator between one streaming platform and unshackle's generic
pipeline. It never downloads or decrypts anything itself; it just answers questions.
The <code>dl</code> command drives the whole flow and only ever calls three required methods on
your service, in order:</p>
<div class="language-text highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>Service.get_titles → which movies / episodes / songs exist
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a> │
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>Service.get_tracks → fetch a manifest for one title …
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a> │
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>manifest.to_tracks → … parsed into Video / Audio / Subtitle tracks
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a> │
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a>Track.download → fetch + merge segments (core-driven)
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a> │
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a>DRM license + decrypt → service license hooks → CDM → decrypt tool
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a> │
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a>Tracks.mux → mkvmerge → tagged .mkv
</span></code></pre></div>
<ol>
<li><strong><code>get_titles()</code></strong> returns a <code>Titles</code> collection: the movies, episodes, or songs
the platform exposes for the requested ID.</li>
<li><strong><code>get_tracks(title)</code></strong> returns a <code>Tracks</code> object for a single chosen title. In
practice this means fetching a manifest and handing it to a parser, which builds
the <code>Video</code>/<code>Audio</code>/<code>Subtitle</code> objects.</li>
<li><strong><code>get_chapters(title)</code></strong> returns any chapter markers.</li>
</ol>
<p>Everything after that (track selection, licensing, decryption, repacking, subtitle
conversion, and muxing) is handled by the core using the objects your service
returned. Your job is to produce accurate <code>Title</code> and <code>Track</code> objects; the framework
does the rest.</p>
<h2 id="the-service-base-class">The <code>Service</code> base class<a class="headerlink" href="#the-service-base-class" title="Permanent link">&para;</a></h2>
<p>Every service subclasses <code>unshackle.core.service.Service</code>, an abstract base class.
Two kinds of members matter: <strong>class variables</strong> that describe the service, and
<strong>methods</strong> that the pipeline calls.</p>
<h3 id="class-variables">Class variables<a class="headerlink" href="#class-variables" title="Permanent link">&para;</a></h3>
<p>Declared at the top of your service class, these are static descriptors:</p>
<table>
<thead>
<tr>
<th>Class variable</th>
<th>Type</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ALIASES</code></td>
<td><code>tuple[str, ...]</code></td>
<td>Alternate tags the service answers to (resolved by <code>Services.get_tag</code>).</td>
</tr>
<tr>
<td><code>GEOFENCE</code></td>
<td><code>tuple[str, ...]</code></td>
<td>ISO region codes the service requires; empty means no geofence. The first entry is treated as the "main region" for automatic proxy selection.</td>
</tr>
<tr>
<td><code>VAULT_TAG</code></td>
<td><code>Optional[str]</code></td>
<td>Overrides the key-vault namespace; defaults to the service's own tag.</td>
</tr>
<tr>
<td><code>AUTH_METHODS</code></td>
<td><code>Optional[tuple[str, ...]]</code></td>
<td>Accepted auth methods (<code>"cookies"</code> / <code>"credentials"</code>). When <code>None</code>, the REST <code>/services</code> endpoint infers them from <code>authenticate()</code>.</td>
</tr>
</tbody>
</table>
<h3 id="required-methods">Required methods<a class="headerlink" href="#required-methods" title="Permanent link">&para;</a></h3>
<p>These are abstract; a service will not load unless it implements all three:</p>
<ul>
<li><code>get_titles() -&gt; Titles_T</code>: resolve the requested ID into a title collection.</li>
<li><code>get_tracks(title) -&gt; Tracks</code>: build the track set for one title.</li>
<li><code>get_chapters(title) -&gt; Chapters</code>: return chapter markers (may be empty).</li>
</ul>
<h3 id="optional-methods-and-hooks">Optional methods and hooks<a class="headerlink" href="#optional-methods-and-hooks" title="Permanent link">&para;</a></h3>
<ul>
<li><strong><code>authenticate(cookies=None, credential=None)</code></strong>: the base implementation loads a
<code>CookieJar</code> into <code>self.session.cookies</code> and stores <code>self.credential</code>. Override it to
perform a real login handshake.</li>
<li><strong><code>search()</code></strong>: raises <code>NotImplementedError</code> by default; implement it to support the
<code>search</code> command.</li>
<li><strong>DRM license hooks</strong>: <code>get_widevine_service_certificate</code>, <code>get_widevine_license</code>,
<code>get_playready_license</code> (delegates to Widevine by default), and
<code>get_clearkey_license</code> (returns <code>None</code> by default). See <a href="#drm-hooks">DRM hooks</a> below.</li>
<li><strong><code>request_input(prompt)</code></strong>: prompts the user; routed through the serve-mode input
bridge or the rich console.</li>
<li><strong>Event callbacks</strong>: <code>on_segment_downloaded</code>, <code>on_track_downloaded</code>,
<code>on_track_decrypted</code>, <code>on_track_repacked</code>, <code>on_track_multiplex</code> let a service react to
pipeline events (for example, MonaLisa decrypts each segment in <code>on_segment_downloaded</code>).</li>
<li><strong><code>resolve_server_keys(title)</code></strong>: an escape hatch for services whose decryption keys are
resolved server-side <em>ahead of</em> the normal per-track license flow. Unlike everything else
here it is <strong>not declared on the <code>Service</code> base class at all</strong>: <code>dl.py</code> probes for it with
<code>hasattr</code> and calls it (once, after track selection) only if your service defines it. Use
it when keys must be fetched or injected in bulk before download, separate from the
per-track license callbacks below.</li>
</ul>
<h3 id="what-__init__-wires-up-for-you">What <code>__init__</code> wires up for you<a class="headerlink" href="#what-__init__-wires-up-for-you" title="Permanent link">&para;</a></h3>
<p>You rarely override <code>__init__</code>; the base constructor takes the Click context and sets
up the objects your methods rely on:</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>What it is</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>self.config</code></td>
<td>The per-service config slice (see <a href="#request-and-session-objects">Request and session objects</a>).</td>
</tr>
<tr>
<td><code>self.log</code></td>
<td>A <code>logging.Logger</code> named after your class.</td>
</tr>
<tr>
<td><code>self.session</code></td>
<td>A ready-to-use HTTP session (headers, retries, and proxy applied).</td>
</tr>
<tr>
<td><code>self.cache</code></td>
<td>A generic key/value <code>Cacher</code> namespaced to the service.</td>
</tr>
<tr>
<td><code>self.title_cache</code></td>
<td>A region- and account-aware <code>TitleCacher</code>.</td>
</tr>
<tr>
<td><code>self.cache_dir</code></td>
<td><code>config.directories.cache / &lt;ServiceName&gt;</code>.</td>
</tr>
<tr>
<td><code>self.credential</code></td>
<td>The active <code>Credential</code>, or <code>None</code> until authenticated.</td>
</tr>
<tr>
<td><code>self.track_request</code></td>
<td>A <code>TrackRequest</code> describing the requested codecs, ranges, and best-available flag (see below).</td>
</tr>
</tbody>
</table>
<p>The constructor also resolves proxies: it reads <code>--proxy</code> / <code>--proxy-query</code> /
<code>--proxy-provider</code>, consults the per-service <code>proxy_map</code>, and, if <code>GEOFENCE</code> is set and
no explicit proxy was given, does a live IP check and auto-fetches a proxy to
<code>GEOFENCE[0]</code> when it detects a geoblock. See <a href="../../guide/proxies-and-vpn/">Proxies and VPN</a>.</p>
<div class="admonition note">
<p class="admonition-title"><code>TrackRequest</code></p>
<p><code>self.track_request</code> is a small dataclass built from the CLI: <code>codecs</code>
(from <code>--vcodec</code>, empty means "accept any"), <code>ranges</code> (from <code>--range</code>, default
<code>[Video.Range.SDR]</code>), and <code>best_available</code>. Use it in <code>get_tracks</code> to decide which
variants to fetch.</p>
</div>
<h2 id="titles-and-the-title-hierarchy">Titles and the <code>Title</code> hierarchy<a class="headerlink" href="#titles-and-the-title-hierarchy" title="Permanent link">&para;</a></h2>
<p><code>get_titles()</code> returns a <strong>title collection</strong>, one of the <code>Titles_T</code> types. Each
collection holds one or more <code>Title</code> objects (<code>Title_T</code>). There are three parallel
families, one per media kind:</p>
<table>
<thead>
<tr>
<th>Media kind</th>
<th>Item (<code>Title_T</code>)</th>
<th>Collection (<code>Titles_T</code>)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Movies</td>
<td><code>Movie</code></td>
<td><code>Movies</code></td>
</tr>
<tr>
<td>TV</td>
<td><code>Episode</code></td>
<td><code>Series</code></td>
</tr>
<tr>
<td>Music</td>
<td><code>Song</code></td>
<td><code>Album</code>, and <code>Music</code> (the base collection)</td>
</tr>
</tbody>
</table>
<p>All items subclass <code>Title</code>, which carries the shared identity: a <strong>unique <code>id</code></strong>, the
originating <code>service</code> class, an optional original <code>language</code>, arbitrary <code>data</code>, and a
freshly-constructed <code>self.tracks = Tracks()</code> that <code>get_tracks</code> will fill. Collections
are sorted lists, so titles stay in a stable order.</p>
<div class="admonition warning">
<p class="admonition-title">The unique ID is validated</p>
<p><code>Title.__init__</code> rejects a falsy <code>id_</code>, and rejects any ID shorter than 4 characters
(<code>"The unique ID is not large enough, clash likely."</code>). Pick a stable, sufficiently
unique identifier from the platform.</p>
</div>
<h3 id="constructors-at-a-glance">Constructors at a glance<a class="headerlink" href="#constructors-at-a-glance" title="Permanent link">&para;</a></h3>
<p>Each subclass adds its own required fields on top of <code>id_</code> and <code>service</code>:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:3"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Movie</label><label for="__tabbed_1_2">Episode</label><label for="__tabbed_1_3">Song</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
<div class="language-python highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="n">Movie</span><span class="p">(</span><span class="n">id_</span><span class="p">,</span> <span class="n">service</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">year</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">language</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</span></code></pre></div>
<p><code>name</code> is required; <code>year</code> is optional but must be a positive integer if given.</p>
</div>
<div class="tabbed-block">
<div class="language-python highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a><span class="n">Episode</span><span class="p">(</span><span class="n">id_</span><span class="p">,</span> <span class="n">service</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">season</span><span class="p">,</span> <span class="n">number</span><span class="p">,</span>
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2" href="#__codelineno-2-2"></a> <span class="n">name</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">year</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">language</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3" href="#__codelineno-2-3"></a> <span class="n">description</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">air_date</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</span></code></pre></div>
<p><code>title</code> is the <strong>show</strong> name; <code>season</code> and <code>number</code> are required (0 is allowed).
An <code>air_date</code> is normalized to a <code>date</code> so the <code>{date}</code> naming token stays clean,
and a placeholder episode <code>name</code> like <code>"Episode 4"</code> is discarded automatically.</p>
</div>
<div class="tabbed-block">
<div class="language-python highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="n">Song</span><span class="p">(</span><span class="n">id_</span><span class="p">,</span> <span class="n">service</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">artist</span><span class="p">,</span> <span class="n">album</span><span class="p">,</span> <span class="n">track</span><span class="p">,</span> <span class="n">disc</span><span class="p">,</span> <span class="n">year</span><span class="p">,</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a> <span class="n">language</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</span></code></pre></div>
<p><code>name</code>, <code>artist</code>, <code>album</code>, <code>track</code>, <code>disc</code>, and <code>year</code> are all required and validated.</p>
</div>
</div>
</div>
<h3 id="title-remapping">Title remapping<a class="headerlink" href="#title-remapping" title="Permanent link">&para;</a></h3>
<p>After titles are fetched (and after any cache), the framework applies
<code>remap_titles(titles, title_map)</code> using the per-service <code>title_map</code> config. This does an
<strong>exact-string</strong> rename (<code>Episode</code>s are matched on their show <code>title</code>, <code>Movie</code>s and
<code>Song</code>s on their <code>name</code>), so a platform's odd naming can be normalized to what your
library expects. Titles are also cache-wrapped via <code>get_titles_cached()</code>, which is region-
and account-aware; see the <a href="#request-and-session-objects">TitleCacher</a> notes.</p>
<p>The ordering here is deliberate on both sides. <code>title_map</code> is applied <strong>after</strong> the title
cache so that editing the map takes effect immediately, without forcing a cache reset, and
<strong>before</strong> metadata enrichment (<code>--enrich</code>) so that an explicit enrich override still wins
over a mapped name.</p>
<h2 id="tracks-and-the-track-model">Tracks and the <code>Track</code> model<a class="headerlink" href="#tracks-and-the-track-model" title="Permanent link">&para;</a></h2>
<p>A <code>Tracks</code> object is the container <code>get_tracks</code> returns. It has typed buckets
(<code>videos</code>, <code>audio</code>, <code>subtitles</code>, <code>chapters</code>, and <code>attachments</code>) plus a <code>manifest_url</code>.
Iterating a <code>Tracks</code> yields <strong>only</strong> video, audio, and subtitle tracks (not chapters or
attachments), and the canonical display/mux order is Video → Audio → Subtitle → Chapter
→ Attachment.</p>
<div class="admonition warning">
<p class="admonition-title">Track IDs must be deterministic</p>
<p><code>Tracks.add</code> refuses duplicate IDs. Track IDs are CRC32-derived from the track's
identity (URL, codec, language, bitrate) and <strong>must be stable across requests</strong>, never
random. The manifest parsers already do this correctly; if you build tracks by hand,
keep IDs deterministic or <code>add</code> will raise.</p>
</div>
<h3 id="the-base-track">The base <code>Track</code><a class="headerlink" href="#the-base-track" title="Permanent link">&para;</a></h3>
<p><code>Track</code> is the base for <code>Video</code>, <code>Audio</code>, and <code>Subtitle</code>. (<code>Chapter</code>, <code>Chapters</code>, and
<code>Attachment</code> are standalone classes, <strong>not</strong> <code>Track</code> subclasses.) Every track knows how
it is delivered, via <code>Track.Descriptor</code>:</p>
<table>
<thead>
<tr>
<th>Descriptor</th>
<th>Delivery</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>URL</code></td>
<td>A single direct URL.</td>
</tr>
<tr>
<td><code>HLS</code></td>
<td>HTTP Live Streaming.</td>
</tr>
<tr>
<td><code>DASH</code></td>
<td>Dynamic Adaptive Streaming over HTTP.</td>
</tr>
<tr>
<td><code>ISM</code></td>
<td>Smooth Streaming.</td>
</tr>
</tbody>
</table>
<p>Key attributes include <code>url</code> (a string or list of strings), <code>language</code> (a
<code>langcodes.Language</code>), <code>drm</code> (an optional iterable of DRM objects), <code>needs_repack</code>, and
descriptor-specific state stashed in <code>track.data["hls"]</code> / <code>track.data["dash"]</code>. When
<code>name</code> is omitted, a human-readable name is derived from the language automatically.</p>
<h3 id="video"><code>Video</code><a class="headerlink" href="#video" title="Permanent link">&para;</a></h3>
<p><code>Video</code> adds <code>codec</code>, <code>range</code>, <code>bitrate</code>, <code>width</code>, <code>height</code>, <code>fps</code>, <code>scan_type</code>, and
closed-caption descriptors. The relevant enums:</p>
<ul>
<li><strong><code>Video.Codec</code></strong>: <code>AVC</code> (H.264), <code>HEVC</code> (H.265), <code>VC1</code>, <code>VP8</code>, <code>VP9</code>, <code>AV1</code>.</li>
<li><strong><code>Video.Range</code></strong>: <code>SDR</code>, <code>HLG</code>, <code>HDR10</code>, <code>HDR10P</code> (HDR10+), <code>DV</code> (Dolby Vision), and
<code>HYBRID</code>. <code>HYBRID</code> is special: it tells the pipeline to select both an HDR10(+) base and
a DV track and merge the DV RPU onto the HDR10 stream with <code>dovi_tool</code> (see
<a href="#dolby-vision-and-hybrid">Dolby Vision hybrid</a>).</li>
</ul>
<div class="admonition note">
<p class="admonition-title">Bitrate units</p>
<p>Both <code>Video</code> and <code>Audio</code> store <code>bitrate</code> in <strong>bytes per second</strong> (rounded up), even
though the string form displays kb/s.</p>
</div>
<h3 id="audio"><code>Audio</code><a class="headerlink" href="#audio" title="Permanent link">&para;</a></h3>
<p><code>Audio</code> adds <code>bitrate</code>, <code>channels</code>, <code>joc</code> (Joint Object Coding, the Atmos indicator),
and <code>descriptive</code>. <code>Audio.Codec</code> covers <code>AAC</code>, <code>AC3</code> (<code>DD</code>), <code>EC3</code> (<code>DD+</code>), <code>AC4</code>,
<code>OPUS</code>, <code>OGG</code> (Vorbis), <code>DTS</code>, <code>ALAC</code>, and <code>FLAC</code>. The <code>atmos</code> property is <code>True</code> when
<code>joc</code> is set (or when <code>extra</code> flags it).</p>
<h3 id="subtitle"><code>Subtitle</code><a class="headerlink" href="#subtitle" title="Permanent link">&para;</a></h3>
<p><code>Subtitle</code> adds three mutually-constrained flags: <code>cc</code> (closed captions), <code>sdh</code>
(subtitles for the deaf and hard-of-hearing), and <code>forced</code>. A track cannot be both <code>cc</code>
and <code>sdh</code>, and <code>forced</code> cannot combine with either. <code>Subtitle.Codec</code> includes <code>SubRip</code>
(SRT), <code>WebVTT</code> (VTT), <code>SubStationAlpha</code>/<code>SubStationAlphav4</code> (SSA/ASS),
<code>TimedTextMarkupLang</code> (TTML), <code>SAMI</code>, and the box-encapsulated DASH formats <code>fTTML</code>
(STPP) and <code>fVTT</code> (WVTT). Subtitles receive post-download normalization
(segment merging, WebVTT sanitization, format conversion, and optional SDH stripping),
all documented for users in <a href="../../guide/subtitles/">Subtitles</a>.</p>
<h3 id="chapters-and-attachments">Chapters and attachments<a class="headerlink" href="#chapters-and-attachments" title="Permanent link">&para;</a></h3>
<ul>
<li><strong><code>Chapter(timestamp, name=None)</code></strong>: a single marker; the timestamp accepts
<code>HH:MM:SS[.mmm]</code>, integer milliseconds, or float seconds.</li>
<li><strong><code>Chapters</code></strong>: a sorted collection that auto-inserts a <code>00:00:00.000</code> chapter and
reads/writes OGM Simple format.</li>
<li><strong><code>Attachment</code></strong>: a font or image attached to the mux (e.g. fonts an ASS subtitle
references), built from a local <code>path</code> or a <code>url</code>.</li>
</ul>
<h3 id="selection-sorting-and-muxing">Selection, sorting, and muxing<a class="headerlink" href="#selection-sorting-and-muxing" title="Permanent link">&para;</a></h3>
<p><code>Tracks</code> is also the selection engine the <code>dl</code> command uses. It offers
<code>sort_videos</code> / <code>sort_audio</code> / <code>sort_subtitles</code> (P2P-standard ordering), predicate-based
<code>select_*</code> filters, <code>by_resolutions</code>, and <code>by_language</code>. When downloads finish, the same
object multiplexes everything: <strong><code>Tracks.mux(title, ...)</code></strong> builds and runs an
<code>mkvmerge</code> command, applying language, default-track, forced, and hearing-impaired flags,
attaching chapters and fonts, and writing a <code>.muxed.mkv</code> (or <code>.mka</code> / <code>.mks</code> for
audio- or subtitle-only sets). As a service author you never call <code>mux</code> yourself; you
just return correct tracks.</p>
<h2 id="manifest-parsers">Manifest parsers<a class="headerlink" href="#manifest-parsers" title="Permanent link">&para;</a></h2>
<p>Most services do not build tracks by hand; they hand a manifest to one of three parsers
in <code>unshackle.core.manifests</code>. Each parser has the same three-part contract:</p>
<table>
<thead>
<tr>
<th>Format</th>
<th>Class</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>MPEG-DASH (<code>.mpd</code>)</td>
<td><code>DASH</code></td>
<td>Parses <code>&lt;MPD&gt;</code>; enumerates tracks from the first content period.</td>
</tr>
<tr>
<td>HLS (<code>.m3u8</code>)</td>
<td><code>HLS</code></td>
<td>Requires a <strong>variant (master) playlist</strong>, not a media playlist.</td>
</tr>
<tr>
<td>Smooth Streaming (<code>.ism</code>)</td>
<td><code>ISM</code></td>
<td>Synthesizes init segments from <code>CodecPrivateData</code>.</td>
</tr>
</tbody>
</table>
<h3 id="the-parser-contract">The parser contract<a class="headerlink" href="#the-parser-contract" title="Permanent link">&para;</a></h3>
<ol>
<li><strong>Construct</strong>: <code>DASH.from_url(url, session=...)</code> or <code>DASH.from_text(text, url)</code>
(and likewise for <code>HLS</code> / <code>ISM</code>). A <code>url</code> is always required, even for <code>from_text</code>,
because relative segment paths are resolved against it.</li>
<li><strong><code>to_tracks(...)</code></strong>: parses the manifest into a <code>Tracks</code> container of <code>Video</code>,
<code>Audio</code>, and <code>Subtitle</code> objects, each carrying descriptor-specific state.</li>
<li><strong><code>download_track(...)</code></strong>: a staticmethod the core calls to fetch, decrypt, and merge
segments. You do not call it; you just return the tracks it will operate on.</li>
</ol>
<p>A typical <code>get_tracks</code> therefore looks like fetching the manifest text with
<code>self.session</code> and returning <code>DASH.from_text(mpd, url).to_tracks(language=title.language)</code>.</p>
<div class="admonition tip">
<p class="admonition-title">Pass the original language</p>
<p>DASH and HLS <code>to_tracks</code> raise <code>ValueError</code> if a track's language cannot be derived
and no valid fallback was supplied. Always pass the title's original recorded
language. For HLS this fallback is used directly for the primary track, since HLS
variants carry no language.</p>
</div>
<h3 id="segment-filtering-hooks">Segment-filtering hooks<a class="headerlink" href="#segment-filtering-hooks" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>HLS</strong>: set <code>track.OnSegmentFilter(segment)</code> to drop unwanted segments (ads/bumpers).</li>
<li><strong>DASH</strong>: pass a <code>period_filter</code> callable to <code>to_tracks</code> to drop whole periods.
Amazon caching-breadth periods and non-<code>content</code> segment types are dropped automatically.</li>
</ul>
<h3 id="format-gotchas-worth-knowing">Format gotchas worth knowing<a class="headerlink" href="#format-gotchas-worth-knowing" title="Permanent link">&para;</a></h3>
<ul>
<li><strong>DASH</strong> enumerates tracks from only the <em>first</em> content period, but downloads segments
from <em>all</em> content periods matching the representation. <code>SegmentTemplate</code>,
<code>SegmentList</code>, and <code>SegmentBase</code> addressing and <code>$Number%0Nd$</code> padding are handled.</li>
<li><strong>HLS</strong> re-fetches the media playlist at download time, recovers <code>SUPPLEMENTAL-CODECS</code>
from raw text (python-m3u8 drops it) to detect Dolby Vision composite streams, and uses
the media-sequence-number IV for AES-128 keys that omit an explicit IV.</li>
<li><strong>ISM</strong> has no segment list and no init segments: segment URLs are synthesized from the
<code>&lt;c&gt;</code> fragment timeline, and a <code>moov</code>/<code>ftyp</code> init box is rebuilt from <code>CodecPrivateData</code>
(supported for AVC, HEVC, DV-over-HEVC, AAC, EC-3, and TTML).</li>
</ul>
<h2 id="drm-hooks">DRM hooks<a class="headerlink" href="#drm-hooks" title="Permanent link">&para;</a></h2>
<p>If a track carries DRM, the framework licenses and decrypts it, but it needs your
service to talk to the platform's license server. unshackle recognizes five DRM systems:</p>
<table>
<thead>
<tr>
<th>DRM class</th>
<th>System</th>
<th>License flow</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Widevine</code></td>
<td>Widevine</td>
<td>CDM challenge → license server → content keys (<code>.wvd</code> device)</td>
</tr>
<tr>
<td><code>PlayReady</code></td>
<td>PlayReady</td>
<td>CDM challenge (WRM header) → license server → content keys (<code>.prd</code> device)</td>
</tr>
<tr>
<td><code>ClearKeyCENC</code></td>
<td>W3C <code>org.w3.clearkey</code> over CENC</td>
<td>JSON KID request → JWK set; no CDM</td>
</tr>
<tr>
<td><code>ClearKey</code></td>
<td>HLS AES-128</td>
<td>key fetched directly from a URI; no CDM, no challenge</td>
</tr>
<tr>
<td><code>MonaLisa</code></td>
<td>Proprietary WASM DRM</td>
<td>keys extracted locally; no license server</td>
</tr>
</tbody>
</table>
<div class="admonition warning">
<p class="admonition-title"><code>ClearKey</code> vs <code>ClearKeyCENC</code></p>
<p>Despite the shared prefix these are entirely different systems. <code>ClearKey</code> is HLS
AES-128, decrypted in-process. <code>ClearKeyCENC</code> is the W3C EME JWK flow over MPEG-CENC,
decrypted with shaka-packager or mp4decrypt like Widevine.</p>
</div>
<h3 id="the-service-side-hooks">The service-side hooks<a class="headerlink" href="#the-service-side-hooks" title="Permanent link">&para;</a></h3>
<p>The framework supplies the CDM and the decryption tool; your service provides the network
calls that the CDM's challenge must be sent through. Override the hooks that your platform
needs:</p>
<table>
<thead>
<tr>
<th>Hook</th>
<th>Return</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>get_widevine_service_certificate(title, track, challenge)</code></td>
<td>cert bytes</td>
<td>Optional privacy-mode service certificate.</td>
</tr>
<tr>
<td><code>get_widevine_license(title, track, challenge)</code></td>
<td>license bytes</td>
<td>POST the Widevine challenge to the license server, return the response.</td>
</tr>
<tr>
<td><code>get_playready_license(title, track, challenge)</code></td>
<td>license</td>
<td>Same for PlayReady; defaults to delegating to Widevine.</td>
</tr>
<tr>
<td><code>get_clearkey_license(title, track, challenge)</code></td>
<td>dict / str / bytes / <code>None</code></td>
<td>For <code>ClearKeyCENC</code>; return <code>None</code> to let the framework POST to the manifest <code>Laurl</code>.</td>
</tr>
</tbody>
</table>
<p>You do not open CDM sessions or run shaka-packager yourself. The <code>Widevine</code>/<code>PlayReady</code>
classes drive the CDM handshake and invoke the decryption tool. Your hook is just the
"send this challenge, give me back the license" step.</p>
<h3 id="how-a-cdm-is-chosen-and-keys-are-found">How a CDM is chosen and keys are found<a class="headerlink" href="#how-a-cdm-is-chosen-and-keys-are-found" title="Permanent link">&para;</a></h3>
<p>CDM selection is config-driven: the <code>cdm</code> map resolves a service tag (and optionally the
track quality or DRM type) to a device name, which <code>load_cdm</code> instantiates as a local
<code>.wvd</code>/<code>.prd</code> or a remote CDM. Before ever requesting a license, the framework tries an
in-process key cache and the configured <strong>key vaults</strong>; only unresolved KIDs trigger an
actual license request, and newly licensed keys are written back to every vault. This is
why the same key is rarely re-licensed twice. Users configure all of this in
<a href="../../guide/drm-and-cdm/">DRM and CDM</a> and <a href="../../guide/vaults/">Key vaults</a>; the API
surface is documented in the <a href="../../reference/api/drm/">DRM API reference</a>.</p>
<h3 id="dolby-vision-and-hybrid">Dolby Vision and hybrid<a class="headerlink" href="#dolby-vision-and-hybrid" title="Permanent link">&para;</a></h3>
<p>Two DV mechanisms sit on top of the track model. <strong>DV fixup</strong> re-exposes DV signaling on
HLS streams that carry a valid DV RPU but advertise plain HEVC (<code>dv_compatible_bitstream</code>).
<strong>Hybrid</strong> processing (<code>Video.Range.HYBRID</code>) selects an HDR10(+) base and a DV donor,
extracts the RPU with <code>dovi_tool</code>, and injects it onto the base layer, producing a single
stream with both HDR10 and DV metadata. Both are automatic once the right tracks are
selected; a service only needs to expose accurate ranges.</p>
<h2 id="request-and-session-objects">Request and session objects<a class="headerlink" href="#request-and-session-objects" title="Permanent link">&para;</a></h2>
<p>Everything a service does over the network flows through objects the base constructor
prepared, all derived from the Click context (<code>ctx</code>) that <code>dl</code> passes in.</p>
<ul>
<li><strong><code>self.config</code></strong>: your service's slice of <code>unshackle.yaml</code>, i.e.
<code>config.services[&lt;ServiceName&gt;]</code>. This is a plain dict; use it for per-service settings
like base URLs, an API key, or a <code>title_map</code>. See
<a href="../../getting-started/configuration-file/">Configuration</a>.</li>
<li><strong><code>self.session</code></strong>: a <code>requests.Session</code> with <code>config.headers</code> applied, a retry policy
(<code>total=5</code>, backoff, retry on 429/5xx), and any resolved proxy already mounted. Pass this
session into the manifest parsers and DRM calls so headers, cookies, and proxies flow
everywhere. (unshackle also ships an rnet-based TLS-fingerprinting session used by the
newer <code>session()</code> factory and configured via the <code>network</code> config key.)</li>
<li><strong><code>self.credential</code></strong>: the active <code>Credential</code> (<code>username:password[:extra]</code>), populated
by <code>authenticate</code>. Its <code>sha1</code> is used as the account hash for cache keys.</li>
<li><strong><code>self.cache</code></strong>: a generic <code>Cacher</code>: JSON key/value storage under the service's cache
dir, with CRC32 integrity checks, versioning, and optional expiry (auto-derived from a
JWT <code>exp</code> claim when present). Good for auth tokens and API responses.</li>
<li><strong><code>self.title_cache</code></strong>: a <code>TitleCacher</code> that wraps <code>get_titles</code> results, keyed by title
ID, region, and account hash. It serves fresh data within <code>title_cache_time</code>, falls back
to stale data within <code>title_cache_max_retention</code> when an API call fails, and is bypassed
by <code>--no-cache</code>.</li>
<li><strong><code>self.track_request</code></strong>: the <code>TrackRequest</code> describing what the user asked for
(codecs, ranges, best-available), for use inside <code>get_tracks</code>.</li>
</ul>
<div class="admonition example">
<p class="admonition-title">A minimal service skeleton</p>
<div class="language-python highlight"><span class="filename">services/EXMP/__init__.py</span><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="kn">from</span><span class="w"> </span><span class="nn">unshackle.core.service</span><span class="w"> </span><span class="kn">import</span> <span class="n">Service</span>
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a><span class="kn">from</span><span class="w"> </span><span class="nn">unshackle.core.manifests</span><span class="w"> </span><span class="kn">import</span> <span class="n">DASH</span>
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a><span class="kn">from</span><span class="w"> </span><span class="nn">unshackle.core.titles</span><span class="w"> </span><span class="kn">import</span> <span class="n">Movie</span><span class="p">,</span> <span class="n">Movies</span>
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a>
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a>
</span><span id="__span-4-6"><a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a><span class="k">class</span><span class="w"> </span><span class="nc">EXMP</span><span class="p">(</span><span class="n">Service</span><span class="p">):</span>
</span><span id="__span-4-7"><a id="__codelineno-4-7" name="__codelineno-4-7" href="#__codelineno-4-7"></a> <span class="n">ALIASES</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;example&quot;</span><span class="p">,)</span>
</span><span id="__span-4-8"><a id="__codelineno-4-8" name="__codelineno-4-8" href="#__codelineno-4-8"></a> <span class="n">GEOFENCE</span> <span class="o">=</span> <span class="p">(</span><span class="s2">&quot;us&quot;</span><span class="p">,)</span>
</span><span id="__span-4-9"><a id="__codelineno-4-9" name="__codelineno-4-9" href="#__codelineno-4-9"></a>
</span><span id="__span-4-10"><a id="__codelineno-4-10" name="__codelineno-4-10" href="#__codelineno-4-10"></a> <span class="k">def</span><span class="w"> </span><span class="nf">get_titles</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Movies</span><span class="p">:</span>
</span><span id="__span-4-11"><a id="__codelineno-4-11" name="__codelineno-4-11" href="#__codelineno-4-11"></a> <span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;https://api.example.com/title/</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">title</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span><span id="__span-4-12"><a id="__codelineno-4-12" name="__codelineno-4-12" href="#__codelineno-4-12"></a> <span class="k">return</span> <span class="n">Movies</span><span class="p">([</span>
</span><span id="__span-4-13"><a id="__codelineno-4-13" name="__codelineno-4-13" href="#__codelineno-4-13"></a> <span class="n">Movie</span><span class="p">(</span>
</span><span id="__span-4-14"><a id="__codelineno-4-14" name="__codelineno-4-14" href="#__codelineno-4-14"></a> <span class="n">id_</span><span class="o">=</span><span class="n">data</span><span class="p">[</span><span class="s2">&quot;id&quot;</span><span class="p">],</span>
</span><span id="__span-4-15"><a id="__codelineno-4-15" name="__codelineno-4-15" href="#__codelineno-4-15"></a> <span class="n">service</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="p">,</span>
</span><span id="__span-4-16"><a id="__codelineno-4-16" name="__codelineno-4-16" href="#__codelineno-4-16"></a> <span class="n">name</span><span class="o">=</span><span class="n">data</span><span class="p">[</span><span class="s2">&quot;name&quot;</span><span class="p">],</span>
</span><span id="__span-4-17"><a id="__codelineno-4-17" name="__codelineno-4-17" href="#__codelineno-4-17"></a> <span class="n">year</span><span class="o">=</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;year&quot;</span><span class="p">),</span>
</span><span id="__span-4-18"><a id="__codelineno-4-18" name="__codelineno-4-18" href="#__codelineno-4-18"></a> <span class="n">language</span><span class="o">=</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;language&quot;</span><span class="p">),</span>
</span><span id="__span-4-19"><a id="__codelineno-4-19" name="__codelineno-4-19" href="#__codelineno-4-19"></a> <span class="p">)</span>
</span><span id="__span-4-20"><a id="__codelineno-4-20" name="__codelineno-4-20" href="#__codelineno-4-20"></a> <span class="p">])</span>
</span><span id="__span-4-21"><a id="__codelineno-4-21" name="__codelineno-4-21" href="#__codelineno-4-21"></a>
</span><span id="__span-4-22"><a id="__codelineno-4-22" name="__codelineno-4-22" href="#__codelineno-4-22"></a> <span class="k">def</span><span class="w"> </span><span class="nf">get_tracks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&quot;Tracks&quot;</span><span class="p">:</span>
</span><span id="__span-4-23"><a id="__codelineno-4-23" name="__codelineno-4-23" href="#__codelineno-4-23"></a> <span class="n">mpd_url</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="o">...</span><span class="p">)</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s2">&quot;manifest&quot;</span><span class="p">]</span>
</span><span id="__span-4-24"><a id="__codelineno-4-24" name="__codelineno-4-24" href="#__codelineno-4-24"></a> <span class="n">mpd</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">mpd_url</span><span class="p">)</span><span class="o">.</span><span class="n">text</span>
</span><span id="__span-4-25"><a id="__codelineno-4-25" name="__codelineno-4-25" href="#__codelineno-4-25"></a> <span class="k">return</span> <span class="n">DASH</span><span class="o">.</span><span class="n">from_text</span><span class="p">(</span><span class="n">mpd</span><span class="p">,</span> <span class="n">mpd_url</span><span class="p">)</span><span class="o">.</span><span class="n">to_tracks</span><span class="p">(</span><span class="n">language</span><span class="o">=</span><span class="n">title</span><span class="o">.</span><span class="n">language</span><span class="p">)</span>
</span><span id="__span-4-26"><a id="__codelineno-4-26" name="__codelineno-4-26" href="#__codelineno-4-26"></a>
</span><span id="__span-4-27"><a id="__codelineno-4-27" name="__codelineno-4-27" href="#__codelineno-4-27"></a> <span class="k">def</span><span class="w"> </span><span class="nf">get_chapters</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">title</span><span class="p">):</span>
</span><span id="__span-4-28"><a id="__codelineno-4-28" name="__codelineno-4-28" href="#__codelineno-4-28"></a> <span class="k">return</span> <span class="p">[]</span>
</span><span id="__span-4-29"><a id="__codelineno-4-29" name="__codelineno-4-29" href="#__codelineno-4-29"></a>
</span><span id="__span-4-30"><a id="__codelineno-4-30" name="__codelineno-4-30" href="#__codelineno-4-30"></a> <span class="k">def</span><span class="w"> </span><span class="nf">get_widevine_license</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">challenge</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">track</span><span class="p">):</span>
</span><span id="__span-4-31"><a id="__codelineno-4-31" name="__codelineno-4-31" href="#__codelineno-4-31"></a> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;https://lic.example.com/wv&quot;</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">challenge</span><span class="p">)</span><span class="o">.</span><span class="n">content</span>
</span></code></pre></div>
<p>The directory name (<code>EXMP</code>) must exactly match the class name; that stem is the
service tag. Put your service in one of the <code>directories.services</code> paths.</p>
</div>
<h2 id="where-to-go-next">Where to go next<a class="headerlink" href="#where-to-go-next" title="Permanent link">&para;</a></h2>
<ul>
<li><strong><a href="../creating-a-service/">Creating a Service</a></strong>: a guided walkthrough that builds on this model.</li>
<li><strong><a href="../../reference/api/service/">Service API</a></strong>: exact signatures for the <code>Service</code> base class.</li>
<li><strong><a href="../../reference/api/titles/">Titles API</a></strong> and <strong><a href="../../reference/api/tracks/">Tracks API</a></strong>: the full object reference.</li>
<li><strong><a href="../../reference/api/drm/">DRM API</a></strong>: the DRM and CDM classes in detail.</li>
<li><strong><a href="../../guide/downloading/">Downloading</a></strong>: how the flags a user passes map onto track selection.</li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../creating-a-service/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Creating a Service">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
Creating a Service
</div>
</div>
</a>
<a href="../rest-api/" class="md-footer__link md-footer__link--next" aria-label="Next: Overview">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Overview
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../..", "features": ["content.code.annotate", "content.code.copy", "content.tabs.link", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.sections", "navigation.tabs", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.79ae519e.min.js"></script>
</body>
</html>