mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
3420 lines
102 KiB
HTML
3420 lines
102 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.8.1">
|
|
|
|
|
|
|
|
<title>Service Architecture - unshackle</title>
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="../../assets/stylesheets/main.57a0d79d.min.css">
|
|
|
|
|
|
<link rel="stylesheet" href="../../assets/stylesheets/palette.fb2fffe2.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">
|
|
|
|
<link rel="stylesheet" href="../../assets/extra.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" >
|
|
|
|
|
|
<div class="md-nav__link md-nav__container">
|
|
<a href="../../reference/configuration/" class="md-nav__link ">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Configuration
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<label class="md-nav__link " for="__nav_4" id="__nav_4_label" tabindex="0">
|
|
<span class="md-nav__icon md-icon"></span>
|
|
</label>
|
|
|
|
</div>
|
|
|
|
<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/directories/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Directories
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/services/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Services & Authentication
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/download/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Download & Processing
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/output/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Output & Naming
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/drm/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
DRM & CDM
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/network/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Network & Proxy
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/vaults/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Key Vaults
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/post-scripts/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Post-Scripts
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/misc/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Other Settings
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</a>
|
|
</li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<li class="md-nav__item">
|
|
<a href="../../reference/configuration/example/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Annotated Example
|
|
|
|
|
|
|
|
</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="#decrypting-a-cbcs-file-yourself" class="md-nav__link">
|
|
<span class="md-ellipsis">
|
|
|
|
Decrypting a cbcs file yourself
|
|
|
|
</span>
|
|
</a>
|
|
|
|
</li>
|
|
|
|
<li class="md-nav__item">
|
|
<a href="#how-unshackle-chooses-a-cdm-and-finds-keys" class="md-nav__link">
|
|
<span class="md-ellipsis">
|
|
|
|
How unshackle chooses a CDM and finds keys
|
|
|
|
</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-http-session-objects" class="md-nav__link">
|
|
<span class="md-ellipsis">
|
|
|
|
Request and HTTP 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/dashboard/" class="md-nav__link">
|
|
|
|
|
|
|
|
<span class="md-ellipsis">
|
|
|
|
|
|
Dashboard
|
|
|
|
|
|
|
|
</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="#decrypting-a-cbcs-file-yourself" class="md-nav__link">
|
|
<span class="md-ellipsis">
|
|
|
|
Decrypting a cbcs file yourself
|
|
|
|
</span>
|
|
</a>
|
|
|
|
</li>
|
|
|
|
<li class="md-nav__item">
|
|
<a href="#how-unshackle-chooses-a-cdm-and-finds-keys" class="md-nav__link">
|
|
<span class="md-ellipsis">
|
|
|
|
How unshackle chooses a CDM and finds keys
|
|
|
|
</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-http-session-objects" class="md-nav__link">
|
|
<span class="md-ellipsis">
|
|
|
|
Request and HTTP 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">¶</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 make tracks, the DRM hooks that license and decrypt them,
|
|
and the request and HTTP 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>download</em> titles, 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 write or read a
|
|
service plugin, this page explains what each object is and how the pieces connect.
|
|
For a guided, step-by-step walkthrough, 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">¶</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 only 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 (movie / episode)
|
|
</span><span id="__span-0-12"><a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a> │ a Song skips the muxer and keeps its own container
|
|
</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>The core does everything after that (track selection, licensing, decryption,
|
|
repacking, subtitle conversion, and muxing) with the objects your service
|
|
returned. Your job is to give 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">¶</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 give the details of 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">¶</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, in any case (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. unshackle treats the first entry as the "main region" for automatic proxy selection.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>GEOBLOCK</code></td>
|
|
<td><code>tuple[str, ...]</code></td>
|
|
<td>ISO region codes where the service refuses to work; every other region is allowed. Use it for a service that works worldwide except in its home market.</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>
|
|
<tr>
|
|
<td><code>ANONYMOUS_SEARCH</code></td>
|
|
<td><code>bool</code></td>
|
|
<td><code>search()</code> needs no login and no state from <code>authenticate()</code>. A search then skips the cookie load, <code>authenticate()</code> and the cookie save. Default <code>False</code>.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>ANIME</code></td>
|
|
<td><code>bool</code></td>
|
|
<td>The catalogue is anime, so metadata lookups prefer AniList. A title's own <code>anime</code> flag overrides it.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>DAILY</code></td>
|
|
<td><code>bool</code></td>
|
|
<td>The catalogue is daily/date-based, so unshackle names episodes by air date. A title's own <code>daily</code> flag overrides it.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<h3 id="required-methods">Required methods<a class="headerlink" href="#required-methods" title="Permanent link">¶</a></h3>
|
|
<p>These are abstract. A service will not load unless it defines all three:</p>
|
|
<ul>
|
|
<li><code>get_titles() -> Titles_T</code>: find the title collection for the requested ID.</li>
|
|
<li><code>get_tracks(title) -> Tracks</code>: assemble the track set for one title.</li>
|
|
<li><code>get_chapters(title) -> 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">¶</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
|
|
do a real login handshake.</li>
|
|
<li><strong><code>search()</code></strong>: raises <code>NotImplementedError</code> by default. Write it so that the
|
|
<code>search</code> command can operate. unshackle calls <code>authenticate()</code> first, unless the
|
|
service sets <code>ANONYMOUS_SEARCH</code>.</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. It goes through the remote-dl input
|
|
bridge, the REST download job relay, or the rich console.</li>
|
|
<li><strong><code>wait_for_approval(message, check, timeout, interval=3.0)</code></strong>: shows a message, then
|
|
calls <code>check</code> until it returns a value. Use it for a login that the user approves on
|
|
another device, such as a pair code. A remote-dl session shows the message as a log
|
|
line, and a REST download job shows it as the job's <code>notice</code>.</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.</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">¶</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 merged service config (see <a href="#request-and-http-session-objects">Request and HTTP 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). Send every request through it: with a proxy active, a request from service code that does not go through the proxy logs a warning. A separate HTTP session that goes direct on purpose sets <code>proxy_exempt = True</code>.</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 / <ServiceName></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> and the <code>proxy_query</code> and
|
|
<code>proxy_provider</code> context params that <code>dl</code> derives from it, consults the per-service
|
|
<code>proxy_map</code>, and, if <code>GEOFENCE</code> is set and you did not give an explicit proxy, does a live
|
|
IP check and auto-fetches a proxy to <code>GEOFENCE[0]</code> when it detects a geoblock. A
|
|
<code>GEOBLOCK</code> hit with no <code>GEOFENCE</code> stops the run with a "not available in your region"
|
|
error, because there is no main region to fetch a proxy for; pass <code>--proxy</code> yourself.
|
|
A proxy whose exit is in a <code>GEOBLOCK</code> region stops the run the same way.
|
|
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>), <code>best_available</code>, and <code>generate_dv</code> (from <code>--generate-dv</code>).
|
|
Use it in <code>get_tracks</code> to decide which variants to fetch. A service that fetches
|
|
the HYBRID variants itself must also fetch HDR10P when <code>generate_dv</code> is set and
|
|
the title has no DV track, because unshackle prefers an HEVC HDR10+ base.</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">¶</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
|
|
use 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">¶</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>You must give <code>name</code>. <code>year</code> is optional, but it must be a positive integer if you
|
|
give it.</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. You must give <code>season</code> and <code>number</code>, and <code>0</code> is a
|
|
valid value. unshackle converts an <code>air_date</code> to a <code>date</code> so the <code>{date}</code> naming
|
|
token stays clean, and it discards a placeholder episode <code>name</code> like <code>"Episode 4"</code>
|
|
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="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">year</span><span class="o">=</span><span class="kc">None</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 class="o">...</span><span class="p">)</span>
|
|
</span></code></pre></div>
|
|
<p>You must give <code>name</code>, <code>artist</code>, <code>album</code>, and <code>track</code>, and unshackle validates them. <code>disc</code> defaults to
|
|
1, and <code>year</code> is optional. A single therefore needs no <code>disc</code>, and an undated release
|
|
needs no <code>year</code>.</p>
|
|
<p>A <code>Song</code> also takes optional metadata keywords that the tagger and the naming templates
|
|
read: <code>album_artist</code>, <code>release_type</code>, <code>total_tracks</code>, <code>total_discs</code>, <code>genre</code>,
|
|
<code>explicit</code>, <code>isrc</code>, <code>upc</code>, <code>copyright</code>, <code>label</code>, <code>lyrics</code>, <code>synced_lyrics</code>,
|
|
<code>lyrics_suffix</code>, and <code>artwork_url</code>. Read the class for the current list. Give <code>lyrics</code> as
|
|
plain text: the tagger embeds it as is. With <code>synced_lyrics</code> (timed LRC or TTML) and
|
|
<code>lyrics_suffix</code> (<code>".lrc"</code>, <code>".ttml"</code>), unshackle also saves the timed lyrics beside the
|
|
final audio file under that suffix.
|
|
You can set these fields in <code>get_tracks()</code> too, and a remote client gets them back.</p>
|
|
<p>The tagger also falls back to <code>Song.data</code> for fields the constructor did not get, such
|
|
as the composer or a full release date. It reads the top level of that dict and a
|
|
nested <code>metadata</code> sub-dict, with the nested keys winning, so either shape works.</p>
|
|
<p>From <code>get_tracks()</code>, a music service gives <strong>one <code>Audio</code> track for each codec and
|
|
bitrate it offers for a <code>Song</code></strong>. The framework then selects one with its usual audio
|
|
options, <code>-a</code>/<code>--acodec</code> and <code>--abitrate</code>, and its usual audio sorting. A movie or an
|
|
episode uses the same options. <code>-q</code>/<code>--quality</code> sets the video height only and does not
|
|
apply to music. <code>Audio</code> has no <code>bit_depth</code> or <code>sample_rate</code> field. Therefore <code>bitrate</code>
|
|
separates a hi-res FLAC from a CD FLAC, and the audio sort already ranks it.</p>
|
|
<p>A <code>Song</code> is an ordinary <code>Title</code> on the one generic download path. unshackle has no
|
|
music-specific service API and no separate music download loop. The
|
|
<code>GROUP_AUDIO_DOWNLOADS</code> flag, <code>get_music_track_options</code> and <code>get_music_collection_label</code>
|
|
no longer exist, and a music service does not need them.</p>
|
|
<div class="admonition warning">
|
|
<p class="admonition-title">Restore the container in <code>on_track_downloaded</code></p>
|
|
<p>Every track downloads to a temp file named <code>Audio_<id>.mp4</code> whatever it holds, and
|
|
the final move keeps that suffix. A <code>Song</code> skips the muxer, so its download <strong>is</strong>
|
|
the delivered file, and the tagger picks its metadata format from the suffix.
|
|
Rename the track path to the true container in <code>on_track_downloaded</code>, or a FLAC
|
|
lands as an untaggable <code>.mp4</code>. The event fires twice, once before decryption and
|
|
once at the end, so wait until <code>track.drm</code> is clear before you rename.</p>
|
|
</div>
|
|
<p>The <strong>MUSIC_EXAMPLE</strong> reference service, at <code>unshackle/services/MUSIC_EXAMPLE/</code>, shows
|
|
the full music surface.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<h3 id="title-remapping">Title remapping<a class="headerlink" href="#title-remapping" title="Permanent link">¶</a></h3>
|
|
<p>After the service fetches the titles (and after any cache), the framework applies
|
|
<code>remap_titles(titles, title_map)</code> with the per-service <code>title_map</code> config. This does an
|
|
<strong>exact-string</strong> rename (it compares <code>Episode</code>s on their show <code>title</code>, and <code>Movie</code>s and
|
|
<code>Song</code>s on their <code>name</code>), so you can change a platform's odd naming to what your
|
|
library expects. <code>get_titles_cached()</code> also cache-wraps the titles, and it is region-
|
|
and account-aware. See the <a href="#request-and-http-session-objects">TitleCacher</a> notes.</p>
|
|
<p>The ordering here is deliberate on both sides. The framework applies <code>title_map</code> <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">¶</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 assemble 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">¶</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 records its
|
|
delivery method in <code>Track.Descriptor</code>, which tells unshackle how to download it:</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>The main 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>. If you
|
|
do not give a <code>name</code>, unshackle derives a human-readable name from the language.</p>
|
|
<h3 id="video"><code>Video</code><a class="headerlink" href="#video" title="Permanent link">¶</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>, <code>LCEVC</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 track 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>bits per second</strong> (rounded up). The
|
|
string form displays kb/s, and <code>--vbitrate</code> and <code>--abitrate</code> compare in kb/s.</p>
|
|
</div>
|
|
<h3 id="audio"><code>Audio</code><a class="headerlink" href="#audio" title="Permanent link">¶</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>DTSX</code>, <code>DTSHD</code>, <code>MPEGH</code>, <code>ALAC</code>, <code>FLAC</code>, and <code>MP3</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">¶</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">¶</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 (for example, fonts an ASS subtitle
|
|
references), built from a local <code>path</code> or a <code>url</code>. A <code>url</code> attachment downloads through
|
|
the same downloader as a track, with the same retries. If it still fails, <code>dl</code> stops the
|
|
run with a one-line error.</li>
|
|
</ul>
|
|
<h3 id="selection-sorting-and-muxing">Selection, sorting, and muxing<a class="headerlink" href="#selection-sorting-and-muxing" title="Permanent link">¶</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
|
|
only return correct tracks.</p>
|
|
<h2 id="manifest-parsers">Manifest parsers<a class="headerlink" href="#manifest-parsers" title="Permanent link">¶</a></h2>
|
|
<p>Most services do not assemble tracks by hand. They give 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><MPD></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">¶</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>). Always pass a <code>url</code>, even to <code>from_text</code>,
|
|
because the parser needs it to make relative segment paths absolute.</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 only 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 they cannot derive a track's language
|
|
and you gave no valid fallback. Always pass the title's original recorded
|
|
language. For HLS, <code>to_tracks</code> uses this fallback directly for the primary track,
|
|
because 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">¶</a></h3>
|
|
<ul>
|
|
<li><strong>HLS</strong>: set <code>track.OnSegmentFilter(segment)</code> to drop unwanted segments (ads/bumpers).
|
|
A dropped segment is invisible to the whole download: unshackle ignores its <code>EXT-X-KEY</code>
|
|
and its <code>EXT-X-DISCONTINUITY</code>, so a clear ad break inside an encrypted track does
|
|
not disturb decryption.</li>
|
|
<li><strong>DASH</strong>: pass a <code>period_filter</code> callable to <code>to_tracks</code> to drop whole periods.
|
|
unshackle automatically drops the caching-breadth periods that some services send, and the non-<code>content</code> segment types.</li>
|
|
</ul>
|
|
<h3 id="format-gotchas-worth-knowing">Format gotchas worth knowing<a class="headerlink" href="#format-gotchas-worth-knowing" title="Permanent link">¶</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. The parser reads <code>SegmentTemplate</code>,
|
|
<code>SegmentList</code>, and <code>SegmentBase</code> addressing, and <code>$Number%0Nd$</code> padding.</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 have no explicit IV.</li>
|
|
<li><strong>ISM</strong> has no list of segments and no init segments: the parser synthesizes segment URLs
|
|
from the <code><c></code> fragment timeline, and rebuilds a <code>moov</code>/<code>ftyp</code> init box from <code>CodecPrivateData</code>
|
|
(supported for AVC, HEVC, DV-over-HEVC, AAC, EC-3, and TTML). A track id covers the manifest
|
|
URL, so a title you build from one ISM manifest per codec or range keeps every variant:
|
|
<code>Tracks.add</code> no longer reads the second manifest's tracks as duplicates of the first.</li>
|
|
</ul>
|
|
<h2 id="drm-hooks">DRM hooks<a class="headerlink" href="#drm-hooks" title="Permanent link">¶</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 six 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>content 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>
|
|
<tr>
|
|
<td><code>BlowfishStripe</code></td>
|
|
<td>Blowfish CBC stripe cipher</td>
|
|
<td>the service gives the per-track key; no CDM, 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">¶</a></h3>
|
|
<p>The framework supplies the CDM and the decryption backend. Your service gives the network
|
|
calls that must carry the CDM's challenge. 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 operate shaka-packager yourself. The <code>Widevine</code>/<code>PlayReady</code>
|
|
classes drive the CDM handshake and call the decryption backend. Your hook is only the
|
|
"send this challenge, give me back the license" step.</p>
|
|
<h3 id="decrypting-a-cbcs-file-yourself">Decrypting a cbcs file yourself<a class="headerlink" href="#decrypting-a-cbcs-file-yourself" title="Permanent link">¶</a></h3>
|
|
<p>Some tracks cannot use the framework's decryption: a single cbcs file whose sample entries share
|
|
a zero default KID, or a sample entry whose content key no CDM can license and only a separate
|
|
process can use. For these, set no <code>drm</code> on the track and override two methods:</p>
|
|
<ul>
|
|
<li><code>decrypts_samples(track)</code> returns <code>True</code> for such a track.</li>
|
|
<li><code>get_sample_decrypter(track, entry)</code> returns, for the 1-based <code>stsd</code> entry, the 16-byte
|
|
content key or a <code>cbcs.SampleDecrypter</code>. A <code>SampleDecrypter</code> gets the protected bytes of each sample of
|
|
one fragment, cut to whole AES blocks, and returns plaintexts of the same lengths. It handles
|
|
full-sample cbcs only, not pattern encryption.</li>
|
|
</ul>
|
|
<p>When the download of the track ends, unshackle calls <code>unshackle.core.drm.cbcs.decrypt_file</code>, and
|
|
the progress row shows "Decrypting". Your <code>on_track_downloaded</code> runs before this and gets the
|
|
encrypted file. <code>decrypt_file</code> writes the plaintext back in place and removes the
|
|
protection boxes, so the muxer reads a clear file. In a remote session <code>get_sample_decrypter</code>
|
|
runs on the server: the client sends the samples through the <code>samples</code> route, so a decrypter
|
|
that needs a process on the server also works for a remote client.</p>
|
|
<h3 id="how-unshackle-chooses-a-cdm-and-finds-keys">How unshackle chooses a CDM and finds keys<a class="headerlink" href="#how-unshackle-chooses-a-cdm-and-finds-keys" title="Permanent link">¶</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 cache of content keys and the configured <strong>key vaults</strong>. Only unresolved KIDs
|
|
trigger an actual license request, and the framework writes newly licensed keys back to
|
|
every vault. This is why the same content 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 <a href="../../reference/api/drm/">DRM API reference</a> gives
|
|
the API surface.</p>
|
|
<h3 id="dolby-vision-and-hybrid">Dolby Vision and hybrid<a class="headerlink" href="#dolby-vision-and-hybrid" title="Permanent link">¶</a></h3>
|
|
<p>Two DV mechanisms sit on top of the track model. <strong>DV fixup</strong> re-exposes DV signalling on
|
|
HLS and DASH streams that carry a valid DV RPU but advertise plain HEVC, with DV only in the
|
|
supplemental codecs (<code>dv_compatible_bitstream</code>). <code>--list</code> shows such a track as <code>HDR10+DV</code> or <code>HDR10P+DV</code>.
|
|
A service sets <code>hdr10_base</code> on a DV track that has an HDR10 base layer (DV profile 8.1), and <code>--list</code> shows it as <code>DV+HDR10</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, to make a single
|
|
track that carries both HDR10 metadata and the DV RPU. Both are automatic once the
|
|
pipeline selects the right tracks. A service only needs to give accurate ranges.</p>
|
|
<p>The fixup replaces the track file with a raw HEVC file. A raw HEVC file has no duration and
|
|
FFmpeg cannot seek in it. When a hybrid base also gets the fixup, the pipeline keeps the
|
|
container file of that track until its hybrid step is complete, and the hybrid step reads
|
|
the container file.</p>
|
|
<h2 id="request-and-http-session-objects">Request and HTTP session objects<a class="headerlink" href="#request-and-http-session-objects" title="Permanent link">¶</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>: a plain dict. unshackle loads the service's own <code>config.yaml</code>, then merges
|
|
the user's <code>services.<TAG></code> block from <code>unshackle.yaml</code> over it. Nested keys merge one by one,
|
|
the user's value wins, and a list in the user's block replaces the whole list. Use it for
|
|
per-service settings like base URLs, an API key, or a <code>title_map</code>. See
|
|
<a href="../../reference/configuration/services/">Services & authentication</a> for the override keys.</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
|
|
HTTP session into the manifest parsers and DRM calls so headers, cookies, and proxies flow
|
|
everywhere. (unshackle also ships an rnet-based TLS-fingerprinting HTTP session that the
|
|
newer <code>session()</code> factory uses and that the <code>network</code> config key configures.)</li>
|
|
<li><strong><code>self.credential</code></strong>: the active <code>Credential</code> (<code>username:password[:extra]</code>), populated
|
|
by <code>authenticate</code>. unshackle uses its <code>sha1</code> 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 <code>--no-cache</code>
|
|
bypasses it.</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">"example"</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">"us"</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">-></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">"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">"</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">"id"</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">"name"</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">"year"</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">"language"</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">-></span> <span class="s2">"Tracks"</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">"manifest"</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">"https://lic.example.com/wv"</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 match the class name exactly. 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">¶</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.a7633da7.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.a5f9d56c.min.js"></script>
|
|
|
|
|
|
</body>
|
|
</html> |