Files
unshackle/reference/configuration/post-scripts/index.html
T
2026-08-18 14:27:38 +00:00

2743 lines
64 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/reference/configuration/post-scripts/">
<link rel="prev" href="../vaults/">
<link rel="next" href="../misc/">
<link rel="icon" href="../../../assets/favicon.svg">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
<title>Post-Download Scripts - unshackle</title>
<link rel="stylesheet" href="../../../assets/stylesheets/main.ec1eaa64.min.css">
<link rel="stylesheet" href="../../../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../../../assets/_mkdocstrings.css">
<script>__md_scope=new URL("../../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#post-scripts" 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">
Post-Download Scripts
</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 md-tabs__item--active">
<a href="../" class="md-tabs__link">
Configuration
</a>
</li>
<li class="md-tabs__item">
<a href="../../../dev/creating-a-service/" class="md-tabs__link">
Developer
</a>
</li>
<li class="md-tabs__item">
<a href="../../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--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" checked>
<div class="md-nav__link md-nav__container">
<a href="../" class="md-nav__link ">
<span class="md-ellipsis">
Configuration
</span>
</a>
<label class="md-nav__link " for="__nav_4" id="__nav_4_label" tabindex="">
<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="true">
<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="../directories/" class="md-nav__link">
<span class="md-ellipsis">
Directories
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../services/" class="md-nav__link">
<span class="md-ellipsis">
Services & Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../download/" class="md-nav__link">
<span class="md-ellipsis">
Download & Processing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../output/" class="md-nav__link">
<span class="md-ellipsis">
Output & Naming
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../drm/" class="md-nav__link">
<span class="md-ellipsis">
DRM & CDM
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../network/" class="md-nav__link">
<span class="md-ellipsis">
Network & Proxy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../vaults/" class="md-nav__link">
<span class="md-ellipsis">
Key Vaults
</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">
Post-Download Scripts
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Post-Download Scripts
</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="#post_scripts" class="md-nav__link">
<span class="md-ellipsis">
post_scripts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#how-the-command-runs" class="md-nav__link">
<span class="md-ellipsis">
How the command runs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#variables" class="md-nav__link">
<span class="md-ellipsis">
Variables
</span>
</a>
<nav class="md-nav" aria-label="Variables">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#music-variables" class="md-nav__link">
<span class="md-ellipsis">
Music variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tagging-ids" class="md-nav__link">
<span class="md-ellipsis">
Tagging IDs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sidecar-files" class="md-nav__link">
<span class="md-ellipsis">
Sidecar files
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#modes" class="md-nav__link">
<span class="md-ellipsis">
Modes
</span>
</a>
<nav class="md-nav" aria-label="Modes">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#music" class="md-nav__link">
<span class="md-ellipsis">
Music
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#events" class="md-nav__link">
<span class="md-ellipsis">
Events
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#-postscript" class="md-nav__link">
<span class="md-ellipsis">
--postscript
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-rest-api" class="md-nav__link">
<span class="md-ellipsis">
The REST API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#logging" class="md-nav__link">
<span class="md-ellipsis">
Logging
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#limitations" class="md-nav__link">
<span class="md-ellipsis">
Limitations
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../misc/" class="md-nav__link">
<span class="md-ellipsis">
Other Settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../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--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="0">
<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="false">
<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="../../../dev/creating-a-service/" class="md-nav__link">
<span class="md-ellipsis">
Creating a Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../dev/service-architecture/" class="md-nav__link">
<span class="md-ellipsis">
Service Architecture
</span>
</a>
</li>
<li class="md-nav__item 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="../../../dev/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="0">
<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="../../../dev/rest-api/quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../dev/rest-api/authentication/" class="md-nav__link">
<span class="md-ellipsis">
Authentication
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../dev/rest-api/endpoints/" class="md-nav__link">
<span class="md-ellipsis">
Endpoints
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../dev/rest-api/job-lifecycle/" class="md-nav__link">
<span class="md-ellipsis">
Job Lifecycle
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../dev/rest-api/remote-sessions/" class="md-nav__link">
<span class="md-ellipsis">
Remote Sessions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../../dev/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="../../api/service/" class="md-nav__link">
<span class="md-ellipsis">
Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api/config/" class="md-nav__link">
<span class="md-ellipsis">
Config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api/tracks/" class="md-nav__link">
<span class="md-ellipsis">
Tracks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../api/titles/" class="md-nav__link">
<span class="md-ellipsis">
Titles
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../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="#post_scripts" class="md-nav__link">
<span class="md-ellipsis">
post_scripts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#how-the-command-runs" class="md-nav__link">
<span class="md-ellipsis">
How the command runs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#variables" class="md-nav__link">
<span class="md-ellipsis">
Variables
</span>
</a>
<nav class="md-nav" aria-label="Variables">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#music-variables" class="md-nav__link">
<span class="md-ellipsis">
Music variables
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tagging-ids" class="md-nav__link">
<span class="md-ellipsis">
Tagging IDs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#sidecar-files" class="md-nav__link">
<span class="md-ellipsis">
Sidecar files
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#modes" class="md-nav__link">
<span class="md-ellipsis">
Modes
</span>
</a>
<nav class="md-nav" aria-label="Modes">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#music" class="md-nav__link">
<span class="md-ellipsis">
Music
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#events" class="md-nav__link">
<span class="md-ellipsis">
Events
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#-postscript" class="md-nav__link">
<span class="md-ellipsis">
--postscript
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-rest-api" class="md-nav__link">
<span class="md-ellipsis">
The REST API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#logging" class="md-nav__link">
<span class="md-ellipsis">
Logging
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#limitations" class="md-nav__link">
<span class="md-ellipsis">
Limitations
</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="post-scripts">Post-download scripts<a class="headerlink" href="#post-scripts" title="Permanent link">&para;</a></h1>
<p>Run your own script when a download finishes, with unshackle's metadata passed in as
<code>{variable}</code> placeholders. Use it to hand a finished file to an uploader, send a
notification, or start a library scan.</p>
<h2 id="post_scripts"><code>post_scripts</code><a class="headerlink" href="#post_scripts" title="Permanent link">&para;</a></h2>
<ul>
<li><strong>Type:</strong> <code>list[dict]</code> &nbsp;·&nbsp; <strong>Default:</strong> <code>[]</code> (no scripts run)</li>
</ul>
<div class="language-yaml highlight"><span class="filename">unshackle.yaml</span><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="nt">post_scripts</span><span class="p">:</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="s">"python</span><span class="nv"> </span><span class="s">/opt/upload.py</span><span class="nv"> </span><span class="s">{filepath}</span><span class="nv"> </span><span class="s">--service={service}</span><span class="nv"> </span><span class="s">--season={season}</span><span class="nv"> </span><span class="s">--episode={episode}</span><span class="nv"> </span><span class="s">--tmdb={tmdb}</span><span class="nv"> </span><span class="s">--subs={sidecars}"</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="nt">event</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">success</span><span class="w"> </span><span class="c1"># success | failure</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="w"> </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">file</span><span class="w"> </span><span class="c1"># file | season | run</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="s">"python</span><span class="nv"> </span><span class="s">/opt/notify.py</span><span class="nv"> </span><span class="s">--title={title}</span><span class="nv"> </span><span class="s">--episode={episode}"</span>
</span></code></pre></div>
<p>Every entry that matches the event and mode runs, so one download can feed an uploader and
a notifier without a wrapper script. An entry may also be written as a plain string, which
is the same as <code>{command: "...", event: success, mode: file}</code>.</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Default</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>command</code></td>
<td>required</td>
<td>The command to run, with <code>{variable}</code> placeholders</td>
</tr>
<tr>
<td><code>event</code></td>
<td><code>success</code></td>
<td><code>success</code> when a file was written, <code>failure</code> when the download failed</td>
</tr>
<tr>
<td><code>mode</code></td>
<td><code>file</code></td>
<td><code>file</code>, <code>season</code>, or <code>run</code>. See <a href="#modes">Modes</a></td>
</tr>
</tbody>
</table>
<p>An entry whose <code>event</code> or <code>mode</code> is not one of the listed values is skipped and logs a
warning naming the bad value, once per command. An entry whose <code>command</code> cannot be split
into arguments, such as one with an unbalanced quote, is skipped with a warning too. Neither
stops the download or the other entries.</p>
<h2 id="how-the-command-runs">How the command runs<a class="headerlink" href="#how-the-command-runs" title="Permanent link">&para;</a></h2>
<p>The command is split into arguments <strong>first</strong>, and variables are substituted into those
arguments afterwards. The process runs without a shell.</p>
<p>This is what keeps a title like <code>Bob"; rm -rf ~</code> harmless: a value can never turn into an
extra argument or a shell operator, and you never have to think about quoting. It also
means a path containing spaces always arrives as a single argument on every OS.</p>
<div class="admonition warning">
<p class="admonition-title">Prefix your variables</p>
<p>Titles, episode names and IDs come from the streaming service, not from you. Tokenizing
first stops a value becoming a <em>new</em> argument, but a value on its own can still be a
whole argument. A bare <code>{title}</code> whose value is <code>--upload-file=/etc/passwd</code> would arrive
as an option to your script, not as text. Write <code>--title={title}</code>, not a bare <code>{title}</code>,
so a service can never forge a flag. unshackle refuses to run a command when a
substituted value would become an option-like token (one that starts with <code>-</code>), and logs
a warning instead.</p>
</div>
<div class="admonition warning">
<p class="admonition-title">No shell features, and name your interpreter</p>
<p>Pipes, <code>&amp;&amp;</code>, <code>&gt;</code> redirection, <code>~</code> and globs do not work, because no shell is involved.
If you want them, make the shell explicit: <code>bash -c "..."</code>.</p>
<p>For the same reason there is no interpreter lookup. Write <code>python /opt/upload.py</code>
rather than <code>/opt/upload.py</code>. unshackle will not guess whether you meant the system
Python or the one in its own virtual environment.</p>
<p>On Windows, when a <strong>fixed</strong> part of an argument contains a space, quote the whole
argument (<code>"--out=C:\My Dir\{filename}"</code>), not just the value. Substituted values
never need quoting on any OS.</p>
<p>On Windows, do not target a <code>.bat</code> or <code>.cmd</code> file directly. Windows runs batch files
through <code>cmd.exe</code>, whose argument quoting differs from every other program and has a
history of letting a crafted value break out (CVE-2024-3566). Call the interpreter and
pass the script as an argument (<code>python upload.py</code>, <code>pwsh -File upload.ps1</code>) instead.</p>
</div>
<p>Scripts are fire and forget. unshackle starts the process, writes the command to the debug
log, and carries on. It does not wait for the script, capture its output, time it out, or
change its own exit code when a script fails. A script keeps running if unshackle exits
first, and <kbd>Ctrl</kbd>+<kbd>C</kbd> during a season pack does not kill the scripts
already started.</p>
<div class="admonition note">
<p class="admonition-title">One process per file</p>
<p>In <code>file</code> mode a large season pack starts one process per episode with nothing
throttling them. If your script is heavy, queue the work inside it or use <code>mode: season</code>.</p>
</div>
<h2 id="variables">Variables<a class="headerlink" href="#variables" title="Permanent link">&para;</a></h2>
<p>Metadata comes from the same naming context that produced the file's name, built per output
file. So <code>{quality}</code> and <code>{hdr}</code> always describe the file being passed. Downloading
<code>-q 1080,2160 -r HDR10,SDR</code> produces four files, each with its own invocation and its own
values.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>{filepath}</code></td>
<td>Absolute path of the output file</td>
</tr>
<tr>
<td><code>{filename}</code></td>
<td>Its filename, with extension</td>
</tr>
<tr>
<td><code>{folder}</code></td>
<td>Absolute path of its parent folder</td>
</tr>
<tr>
<td><code>{ext}</code></td>
<td>Its extension, including the dot</td>
</tr>
<tr>
<td><code>{sidecars}</code></td>
<td>Sidecar subtitle files written for this output, newline separated</td>
</tr>
<tr>
<td><code>{title}</code></td>
<td>Show, movie or track name, sanitized</td>
</tr>
<tr>
<td><code>{title_raw}</code></td>
<td>Exactly what the service returned, untouched</td>
</tr>
<tr>
<td><code>{title_id}</code></td>
<td>The service's ID for the title</td>
</tr>
<tr>
<td><code>{service}</code></td>
<td>Service tag</td>
</tr>
<tr>
<td><code>{year}</code> <code>{season}</code> <code>{episode}</code> <code>{episode_name}</code></td>
<td>Season and episode are plain numbers (<code>1</code>, <code>5</code>)</td>
</tr>
<tr>
<td><code>{quality}</code> <code>{resolution}</code> <code>{hdr}</code> <code>{hfr}</code> <code>{vcodec}</code> <code>{acodec}</code> <code>{edition}</code> <code>{tag}</code> <code>{lang_tag}</code></td>
<td>As in the filename template, for this file</td>
</tr>
<tr>
<td><code>{tmdb}</code> <code>{imdb}</code> <code>{tvdb}</code></td>
<td>The tagging IDs in use, otherwise empty. See <a href="#tagging-ids">Tagging IDs</a></td>
</tr>
<tr>
<td><code>{error}</code></td>
<td>Failure message. Empty on success</td>
</tr>
</tbody>
</table>
<p>Every variable from your <code>output_template</code> is available here as well, including
<code>{video}</code> and <code>{audio}</code>, of which <code>{vcodec}</code> and <code>{acodec}</code> are aliases.</p>
<div class="admonition warning">
<p class="admonition-title">Empty variables become an empty string</p>
<p><code>--tmdb={tmdb}</code> with no ID resolved hands your script <code>--tmdb=</code>, so treat an empty
string as absent. Write your script to handle every variable being empty.</p>
<p>Empty is common, and not only when data is missing:</p>
<ul>
<li>Nothing was resolved. <code>{tmdb}</code> is empty when no metadata provider found the title,
the matching API key is missing, or the title is a movie you gave no ID for.</li>
<li>The title has no such field. Movies have no <code>{season}</code> or <code>{episode}</code>, and music has
neither.</li>
<li><strong>The naming context renders it empty on purpose.</strong> <code>{hdr}</code> is empty for an SDR
file, because SDR is the absence of an HDR tag in a filename rather than a tag that
reads <code>SDR</code>. <code>{edition}</code>, <code>{atmos}</code>, <code>{multi}</code>, <code>{dual}</code> and <code>{hfr}</code> behave the same
way. If your script needs the word <code>SDR</code>, derive it from an empty <code>{hdr}</code> yourself.</li>
<li>The hook is a <code>failure</code> hook. A failed download has no output file to read metadata
from, so everything that comes from the naming context is empty. See
<a href="#events">Events</a>.</li>
</ul>
<p>There is no <code>{variable?}</code> conditional here, unlike <code>output_template</code>. The token is
always passed, so the argument list your script receives keeps the same shape on every
run and your argument parser does not have to cope with a flag appearing and
disappearing.</p>
<p>Season and episode are the plain numbers, not the <code>S01E05</code> form used in filenames. The
padding and the separator belong to the filename template rather than to the data.</p>
</div>
<h3 id="music-variables">Music variables<a class="headerlink" href="#music-variables" title="Permanent link">&para;</a></h3>
<p>A music download builds its variables from the music naming context, so a hook gets the
release fields instead of the season and episode ones. <code>{season}</code>, <code>{episode}</code> and
<code>{episode_name}</code> are always empty for music.</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>{artist}</code></td>
<td>Track artist</td>
</tr>
<tr>
<td><code>{album_artist}</code></td>
<td>Album artist, falling back to the track artist</td>
</tr>
<tr>
<td><code>{album}</code></td>
<td>Album name</td>
</tr>
<tr>
<td><code>{track_number}</code> <code>{disc}</code></td>
<td>Zero padded. <code>{disc}</code> is empty on a single-disc release</td>
</tr>
<tr>
<td><code>{track_total}</code> <code>{disc_total}</code></td>
<td>Zero padded totals, empty when the service did not give them</td>
</tr>
<tr>
<td><code>{isrc}</code> <code>{upc}</code> <code>{label}</code> <code>{genre}</code> <code>{release_type}</code> <code>{explicit}</code></td>
<td>As the service reported them, empty when absent</td>
</tr>
</tbody>
</table>
<p>In a <code>file</code> hook <code>{title}</code> is the track name. See <a href="#modes">Modes</a> for what an album hook
puts in <code>{title}</code>.</p>
<h3 id="tagging-ids">Tagging IDs<a class="headerlink" href="#tagging-ids" title="Permanent link">&para;</a></h3>
<p><code>{tmdb}</code>, <code>{imdb}</code> and <code>{tvdb}</code> carry the IDs unshackle is tagging the file with, read at
the moment the hook is dispatched rather than snapshotted at the start of the run. An
episode's TMDB ID is resolved by the title search inside the download loop, so a hook fires
with it already filled in.</p>
<p>They hold an ID you passed with <code>--tmdb</code>, <code>--imdb</code> or <code>--tvdb</code>, plus a TMDB ID the episode
search resolved. No such search runs for a movie, so a movie hook gets them empty unless you
passed one.</p>
<h3 id="sidecar-files">Sidecar files<a class="headerlink" href="#sidecar-files" title="Permanent link">&para;</a></h3>
<p><code>{sidecars}</code> holds the sidecar subtitle files written beside the output, which are the only
extra files a download leaves next to it. Attachments such as fonts are muxed into the
container and temporary artwork is deleted, so neither appears. Expect <code>{sidecars}</code> to be
empty unless <a href="../download/#subtitle"><code>subtitle.output_mode</code></a> is <code>sidecar</code> or <code>both</code>, which is
not the default. A music download writes no sidecars at all.</p>
<p>Paths are absolute and separated by a newline, because a newline is the only character that
cannot appear in a filename on any supported OS. The whole list arrives as a single
argument, so no quoting is needed in your template.</p>
<p>When one download writes several outputs (<code>-q 1080,720</code>), each output's folder gets its own
copy of the sidecar files, and each invocation lists the copies sitting beside its own
<code>{filepath}</code>.</p>
<div class="language-python highlight"><span class="filename">Reading {sidecars} in Python</span><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">subs</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s2">"--subs="</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> <span class="k">if</span> <span class="n">p</span><span class="p">]</span>
</span></code></pre></div>
<h2 id="modes">Modes<a class="headerlink" href="#modes" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th><code>mode</code></th>
<th>Runs</th>
<th><code>{filepath}</code></th>
<th><code>{sidecars}</code></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>file</code></td>
<td>Once per output file</td>
<td>That file</td>
<td>That output's sidecars</td>
</tr>
<tr>
<td><code>season</code></td>
<td>Once per season folder, after that season's last queued title</td>
<td>Empty</td>
<td>Empty</td>
</tr>
<tr>
<td><code>run</code></td>
<td>Once per folder written, at the end of the run</td>
<td>Empty</td>
<td>Empty</td>
</tr>
</tbody>
</table>
<div class="admonition warning">
<p class="admonition-title"><code>season</code> means everything this run asked for</p>
<p>A <code>season</code> hook fires when the last title <strong>this run queued</strong> for that season finishes.
With <code>-w S01-S02</code> the S01 hook runs while S02 is still downloading. It says nothing
about whether the season is complete on disk. Downloading a single episode fires the
hook for that season as soon as the episode lands.</p>
<p>A title that fails takes its group with it. If one episode of S01 fails, the S01 hook
does not fire at all, because the run never finished everything it queued for that
season.</p>
</div>
<p>The hook is keyed to the folder, not to the season, so one season landing in several
folders (<code>-q 1080,720</code> with a quality-dependent folder template) fires it once per folder,
each with its own <code>{folder}</code>. A movie is a group of its own: a <code>season</code> hook fires for a
movie's folder as soon as the movie finishes.</p>
<p>In <code>run</code> mode every variable except <code>{folder}</code> is empty, because one run can cover several
shows. Only <code>{folder}</code> is meaningful, which makes it useful for starting a library scan
once.</p>
<h3 id="music">Music<a class="headerlink" href="#music" title="Permanent link">&para;</a></h3>
<p>Music uses the same three modes, with the album standing in for the season.</p>
<table>
<thead>
<tr>
<th><code>mode</code></th>
<th>Runs</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>file</code></td>
<td>Once per track, after its metadata has been written</td>
</tr>
<tr>
<td><code>season</code></td>
<td>Once per album folder, after every queued track of that album has landed</td>
</tr>
<tr>
<td><code>run</code></td>
<td>Once per folder written, at the end of the run</td>
</tr>
</tbody>
</table>
<p>In a <code>file</code> hook <code>{title}</code> and <code>{title_raw}</code> are the track name. In an album hook they are
the album name, and the per-track variables <code>{track_number}</code>, <code>{disc}</code> and <code>{isrc}</code> are
empty, in the same way that <code>{episode}</code> is empty in a season hook.</p>
<h2 id="events">Events<a class="headerlink" href="#events" title="Permanent link">&para;</a></h2>
<p><code>success</code> runs after the file has moved to its final path. <code>failure</code> runs when the download
fails, with <code>{filepath}</code> empty and <code>{error}</code> set. A music <code>failure</code> hook describes the
release rather than a track, because the whole album stops when one track fails.</p>
<div class="admonition warning">
<p class="admonition-title"><code>failure</code> only fires in <code>file</code> mode</p>
<p>A <code>failure</code> entry with <code>mode: season</code> or <code>mode: run</code> never runs. Leave <code>mode</code> at its
default on a failure entry.</p>
</div>
<p>A <code>failure</code> hook has no output file to read metadata from, so it carries only what the
title object already knew: <code>{title}</code>, <code>{title_raw}</code>, <code>{title_id}</code>, <code>{year}</code>, <code>{season}</code>,
<code>{episode}</code>, <code>{episode_name}</code>, <code>{service}</code>, the tagging IDs and <code>{error}</code>. Everything that
comes from the naming context, such as <code>{quality}</code>, <code>{hdr}</code>, <code>{artist}</code> and <code>{album}</code>, is
empty.</p>
<p>A resumed <code>--continue</code> download that finishes runs <code>success</code> as usual. Because unshackle
does not wait for your script, it never sees its exit code, so a failing script is not
reported anywhere. Your script owns its own error handling and logging.</p>
<p>A <code>success</code> hook always names a file this run wrote. There is no skip-if-exists path in
<code>dl</code> today, so nothing fires for a title that was not downloaded.</p>
<h2 id="-postscript"><code>--postscript</code><a class="headerlink" href="#-postscript" title="Permanent link">&para;</a></h2>
<p><code>dl</code> accepts <code>--postscript "&lt;command&gt;"</code>, repeatable. Every command given this way runs with
<code>event: success</code> and <code>mode: file</code>.</p>
<div class="language-console 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="gp">$ </span>unshackle<span class="w"> </span>dl<span class="w"> </span>--postscript<span class="w"> </span><span class="s2">"python /opt/upload.py {filepath}"</span><span class="w"> </span>SERVICE<span class="w"> </span>TITLE_ID
</span></code></pre></div>
<p>It replaces the <code>post_scripts</code> config for that run rather than adding to it, so no
configured entry runs while it is in use. That includes your <code>season</code>, <code>run</code> and <code>failure</code>
entries, which have no <code>--postscript</code> equivalent.</p>
<h2 id="the-rest-api">The REST API<a class="headerlink" href="#the-rest-api" title="Permanent link">&para;</a></h2>
<p>The API never accepts a post-script command. <code>postscript</code>, <code>post_script</code> and <code>post_scripts</code>
in a <code>POST /api/download</code> body are rejected with <code>400 INVALID_PARAMETERS</code>, because a command
arriving from an HTTP caller would be remote code execution.</p>
<p>Scripts defined in <code>unshackle.yaml</code> do run for API jobs, music jobs included. Only the
command itself may not cross the network.</p>
<h2 id="logging">Logging<a class="headerlink" href="#logging" title="Permanent link">&para;</a></h2>
<p>The command that was run is logged twice: at <code>DEBUG</code> level on the console, and as a
<code>post_script_dispatch</code> entry in the
<a href="../../../guide/troubleshooting/#the-structured-json-debug-log">structured JSON debug log</a>
when that log is enabled.</p>
<p>Both are masked the same way, honouring <a href="../misc/"><code>redact_paths</code></a>, which is on by default.
Debug logs get shared in issue reports, and a hook command line is mostly paths. URL
credentials and <code>token=</code> / <code>api_key=</code> query parameters are also masked.</p>
<div class="admonition warning">
<p class="admonition-title">Do not hardcode secrets in the command</p>
<p>An arbitrary secret in the command line, such as <code>-H "Authorization: Bearer &lt;key&gt;"</code>,
cannot be recognised and is logged verbatim. Read the secret from a file or an
environment variable inside your script rather than putting it in the <code>command</code>.</p>
</div>
<h2 id="limitations">Limitations<a class="headerlink" href="#limitations" title="Permanent link">&para;</a></h2>
<ul>
<li>No <code>success</code> hooks run under <code>--no-mux</code> or <code>--skip-dl</code>, because neither writes a muxed
output. A <code>failure</code> hook still runs under <code>--no-mux</code> if the download itself fails.</li>
<li><code>failure</code> hooks only run in <code>file</code> mode.</li>
<li>There is no webhook sender. Call <code>curl</code> from a script instead.</li>
<li>Script output is not captured. Redirect it inside your own script if you want a log.</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="../vaults/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Key Vaults">
<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">
Key Vaults
</div>
</div>
</a>
<a href="../misc/" class="md-footer__link md-footer__link--next" aria-label="Next: Other Settings">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Other Settings
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "../../..", "features": ["content.code.annotate", "content.code.copy", "content.tabs.link", "content.tooltips", "navigation.footer", "navigation.indexes", "navigation.sections", "navigation.tabs", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../../assets/javascripts/bundle.d7400e89.min.js"></script>
</body>
</html>