From d36797641d0987ba9e2c7488fba0121fa28d8bc0 Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Sun, 6 Sep 2026 21:02:58 -0600 Subject: [PATCH] feat(dl): add --no-postscript to skip post-scripts for one run --- docs/guide/cli-reference.md | 1 + docs/guide/downloading.md | 9 ++++++++- docs/reference/configuration/post-scripts.md | 10 ++++++++++ tests/test_post_scripts.py | 15 +++++++++++++++ unshackle/commands/dl.py | 11 ++++++++++- unshackle/core/utils/post_scripts.py | 2 ++ 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/guide/cli-reference.md b/docs/guide/cli-reference.md index 33866c55..13cd897a 100644 --- a/docs/guide/cli-reference.md +++ b/docs/guide/cli-reference.md @@ -149,6 +149,7 @@ Keep only certain track types, or skip certain track types. | `--tag` | Group tag override. | | `--repack` | Add a `REPACK` tag to the filename. | | `--postscript` | Run a command after each output file, with `{variable}` placeholders substituted. Repeatable. Replaces the `post_scripts` config for this run. See [Post-scripts](../reference/configuration/post-scripts.md). | +| `--no-postscript` | Run no post-script for this run, neither the configured ones nor `--postscript`. | ### Metadata & tagging diff --git a/docs/guide/downloading.md b/docs/guide/downloading.md index 678a0ce5..5dcd9b64 100644 --- a/docs/guide/downloading.md +++ b/docs/guide/downloading.md @@ -698,6 +698,13 @@ that run writes no muxed output. A `failure` post-script still operates if the d unshackle dl --postscript "python /opt/upload.py {filepath} --service={service}" EXAMPLE 81234567 ``` +`--no-postscript` operates the run with no post-script at all. It silences the `post_scripts` +config and `--postscript` together, for every event and mode. + +```shell title="Skip the uploader for one run" +unshackle dl --no-postscript EXAMPLE 81234567 +``` + For post-scripts that continue across runs, for the `season` and `run` modes, for `failure` post-scripts and for the full variable list, see [Post-scripts](../reference/configuration/post-scripts.md). @@ -946,7 +953,7 @@ authoritative list. | `--best-available` | | Degrade gracefully instead of failing. | | `--output` | `-o` | Output directory for this run. | | `--split-audio` / `--merge-video` / `--no-mux` | | Muxing behaviour. | -| `--postscript` | | Run a command after each output file. Repeatable. | +| `--postscript` / `--no-postscript` | | Run a command after each output file, or run none. | | `--proxy` / `--no-proxy` / `--no-proxy-download` | | Proxy control. | | `--workers` / `--downloads` / `--slow` | | Concurrency and pacing. | | `--list` / `--list-titles` / `--skip-dl` | | Dry runs. | diff --git a/docs/reference/configuration/post-scripts.md b/docs/reference/configuration/post-scripts.md index 20e9c910..bf245b3f 100644 --- a/docs/reference/configuration/post-scripts.md +++ b/docs/reference/configuration/post-scripts.md @@ -282,6 +282,16 @@ It replaces the `post_scripts` config for that run rather than adding to it, so configured entry runs while it is in use. That includes your `season`, `run` and `failure` entries, which have no `--postscript` equivalent. +## `--no-postscript` + +`dl --no-postscript` operates the run with no post-script. It silences every configured entry +and every `--postscript` command, for each event and mode. Use it to skip an uploader or a +notifier for one run without an edit to `unshackle.yaml`. + +```console +$ unshackle dl --no-postscript SERVICE TITLE_ID +``` + ## The REST API The API never accepts a post-script command. `postscript`, `post_script` and `post_scripts` diff --git a/tests/test_post_scripts.py b/tests/test_post_scripts.py index 2aa0c495..02274eac 100644 --- a/tests/test_post_scripts.py +++ b/tests/test_post_scripts.py @@ -7,8 +7,12 @@ from types import SimpleNamespace import pytest +import unshackle.core.utils.post_scripts as ps from unshackle.core.config import config from unshackle.core.utils.post_scripts import ( + EVENTS, + MODES, + NO_POST_SCRIPTS, SIDECAR_SEPARATOR, _entries, build_context, @@ -217,3 +221,14 @@ def test_wait_true_waits_for_the_script_and_logs_the_exit_code(monkeypatch, capl def test_wait_defaults_to_fire_and_forget(monkeypatch): slow = 'python -c "import time; time.sleep(0.3)"' assert _dispatch_seconds(monkeypatch, {"command": slow}) < 0.3 + + +def test_no_postscript_override_silences_every_event_and_mode(monkeypatch): + """``dl --no-postscript`` passes NO_POST_SCRIPTS as the override list. Nothing may spawn.""" + monkeypatch.setattr(config, "post_scripts", [{"command": "should_not_run.py", "mode": mode} for mode in MODES]) + spawned: list[list[str]] = [] + monkeypatch.setattr(ps.subprocess, "Popen", lambda argv, **kw: spawned.append(argv)) + for event in EVENTS: + for mode in MODES: + dispatch(event, mode, {"filepath": "/x/y.mkv"}, NO_POST_SCRIPTS) + assert spawned == [] diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 1c99baac..bed700a4 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -99,7 +99,7 @@ from unshackle.core.utils.click_types import ( SubtitleCodecChoice, ) from unshackle.core.utils.collections import ci_get, merge_dict -from unshackle.core.utils.post_scripts import build_context, dispatch, season_context +from unshackle.core.utils.post_scripts import NO_POST_SCRIPTS, build_context, dispatch, season_context from unshackle.core.utils.redact import mask_proxy from unshackle.core.utils.selector import select_multiple from unshackle.core.utils.subprocess import ffprobe @@ -875,6 +875,12 @@ class dl: "variables substituted. Repeatable. Replaces the post_scripts config for this run." ), ) + @click.option( + "--no-postscript", + is_flag=True, + default=False, + help="Do not run any post-script for this run, configured or --postscript.", + ) @click.option( "--workers", type=int, @@ -1533,9 +1539,12 @@ class dl: real_audio_bitrate: bool = False, progress_sink: Optional[Callable[[dict[str, Any]], None]] = None, postscript: Sequence[str] = (), + no_postscript: bool = False, *_: Any, **__: Any, ) -> None: + if no_postscript: + postscript = NO_POST_SCRIPTS if continue_downloads: config.continue_downloads = True self.tmdb_searched = False diff --git a/unshackle/core/utils/post_scripts.py b/unshackle/core/utils/post_scripts.py index afdca3f2..097af245 100644 --- a/unshackle/core/utils/post_scripts.py +++ b/unshackle/core/utils/post_scripts.py @@ -35,6 +35,8 @@ EVENTS = ("success", "failure") MODES = ("file", "season", "run") SIDECAR_SEPARATOR = "\n" +NO_POST_SCRIPTS: tuple[str, ...] = ("",) +"""``dl --no-postscript``: an override list that replaces the config and holds no command to run.""" _warned_entries: set[str] = set()