feat(dl): add --skip-missing to skip titles that miss a required language or track

A season run skips such a title and continues; one title, or a run that skips every title, still exits 1 (now also for -ado/-fso). REST jobs report skipped_titles.
This commit is contained in:
imSp4rky
2026-11-13 18:30:01 -07:00
parent 94dfb5d677
commit 6964f58271
13 changed files with 403 additions and 83 deletions
+6 -3
View File
@@ -451,9 +451,10 @@ Make a download job. It requires `service` and `title_id`. Every other field is
| `latest_episode` | boolean | `false` | Only the newest episode. |
| `latest_episodes` | integer | `null` | Only the N newest episodes. A positive integer. When set, it overrides `latest_episode`. |
| `lang` / `v_lang` / `a_lang` / `s_lang` | string[] | `["orig"]` / `[]` / `[]` / `["all"]` | Language filters. A missing `s_lang` language gives a warning and the job continues. |
| `require_audio` | string[] | `[]` | Audio languages that must exist. The job fails if one is missing, even with `best_available`. |
| `require_video` | string[] | `[]` | Video languages that must exist. The job fails if one is missing, even with `best_available`. |
| `require_subs` | string[] | `[]` | Subtitle languages that must exist. The job fails if one is missing. `s_lang` still decides what to keep. |
| `require_audio` | string[] | `[]` | Audio languages that must exist. The job fails if one is missing, even with `best_available`, or skips the title with `skip_missing`. |
| `require_video` | string[] | `[]` | Video languages that must exist. The job fails if one is missing, even with `best_available`, or skips the title with `skip_missing`. |
| `require_subs` | string[] | `[]` | Subtitle languages that must exist. The job fails if one is missing, or skips the title with `skip_missing`. `s_lang` still decides what to keep. |
| `skip_missing` | boolean | `false` | Skip a title that misses a required language or track, and continue with the next title. The job's `skipped_titles` lists each skip. The job fails when it has one title or skips every title. |
| `forced_subs` | boolean | `false` | Include forced subtitles. |
| `forced_subs_only` | boolean | `false` | Download forced subtitles and no other subtitle; implies `forced_subs`. Not valid with `no_subs`, `video_only`, `audio_only` or `chapters_only`. |
| `forced_s_lang` | string[] | `[]` | Forced subtitle language(s); implies `forced_subs`. |
@@ -585,6 +586,7 @@ Show download jobs, with optional filtering and sorting.
"segments_total": 300,
"speed": "5.2 MB/s",
"skipped_subtitles": [],
"skipped_titles": [],
"input_prompt": null,
"notice": null
}
@@ -853,6 +855,7 @@ Read the persisted history of jobs that reached a terminal state, newest first.
"created_time": "2026-07-03T12:00:00+00:00",
"completed_time": "2026-07-03T12:08:00+00:00",
"output_files": ["/downloads/Example.S01E01.mkv"],
"skipped_titles": [],
"error_message": null,
"parameters": { "...": "redacted" }
}
+1
View File
@@ -162,6 +162,7 @@ full detail:
| `segments_total` | number | Total segments for the track in progress. |
| `speed` | string | Transfer speed of the track in progress, e.g. `4.20 MB/s`. |
| `skipped_subtitles` | list | Subtitles that were skipped as non-fatal failures (when `skip_subtitle_errors` is set). |
| `skipped_titles` | list | Titles that the run skipped because a requested track or language is missing, each `{title, reason}`. |
| `input_prompt` | string or `null` | The prompt the service waits on. Answer it with `POST /api/download/jobs/{job_id}/input`. |
| `notice` | string or `null` | A message the service shows while it waits for an approval on another device, such as a pair code. It needs no answer. |
+1
View File
@@ -102,6 +102,7 @@ unshackle dl [OPTIONS] SERVICE [SERVICE ARGS...]
| `--require-audio` | - | Audio langs that must exist. The title fails even with `--best-available`. Does not select tracks. |
| `--require-video` | - | Video langs that must exist. The title fails even with `--best-available`. Does not select tracks. |
| `--require-subs` | - | Subtitle langs that must exist. Fails the title if one is missing. `--s-lang` still decides what to keep. |
| `--skip-missing` | off | Skip a title that misses a required language or track, and continue with the next title. A run with one title, or a run that skips every title, still fails. |
| `-fs`, `--forced-subs` | off | Include forced subtitle tracks. |
| `-fso`, `--forced-subs-only` | off | Keep only forced subtitle tracks; drop the other subtitles. Implies `-fs`. |
| `-fsl`, `--forced-s-lang` | none | Language(s) wanted for forced subtitles; implies `-fs`. A `-` prefix excludes. |
+14 -1
View File
@@ -531,6 +531,17 @@ The check runs against the full track list, before any language selection or exc
`orig` resolves to the title language, and `--exact-lang` applies. Audio carried inside the
video track counts. `--no-audio` and `--no-video` drop the matching requirement.
A failed title stops the whole run. To skip that title and come back for it later, add
`--skip-missing`, or set `skip_missing: true` under `dl:` in `unshackle.yaml`. It also skips a
title that has no track for a filter such as `-q`, `-v`, `-r`, `-a` or a language you request.
A run with one title, or a run that skips every title, still fails. A REST API job lists each
skipped title and the reason in `skipped_titles`.
```bash
# download the episodes that have Korean audio, skip the others
unshackle dl -al ko,en --require-audio ko --skip-missing -w S06 EXAMPLE 0ABC123
```
### Forced subtitles and output format
- `-fs` / `--forced-subs`: include forced subtitle tracks (signs/foreign dialogue).
@@ -777,7 +788,8 @@ Additional track-type flags:
- A flag that drops the same tracks is an error, for example `-ado` with `--no-audio`
or `-fso` with `-A`.
- A title with no track of that kind stops a run of one title. A run of more than one
title skips that title with a warning and continues.
title skips that title with a warning and continues. The run fails when it skips
every title.
- unshackle reads the kind from the manifest or from the service. A track that
neither of them marks as descriptive or forced does not match.
@@ -1214,6 +1226,7 @@ authoritative list.
| `--require-audio` | | Audio languages that must exist. The title fails if one is missing. |
| `--require-video` | | Video languages that must exist. The title fails if one is missing. |
| `--best-available` | | Degrade gracefully instead of failing. |
| `--skip-missing` | | Skip a title that misses a required language or track, and continue with the next title. |
| `--output` | `-o` | Output directory for this run. |
| `--split-audio` / `--merge-video` / `--no-mux` | | Muxing behaviour. |
| `--postscript` / `--no-postscript` | | Run a command after each output file, or run none. |
+1 -1
View File
@@ -157,7 +157,7 @@ unshackle dl --s-lang en --forced-subs-only EXAMPLE 81234567
`-fso` does not drop video or audio. Add `-S` for a subtitle-only download. A title with
no forced track stops a run of one title. A run of more than one title skips that title
with a warning.
with a warning, and fails when it skips every title.
## Track types: forced, SDH, and CC
+1
View File
@@ -53,6 +53,7 @@ Common config keys. This is a useful subset, and every `dl` flag works:
| `speed_limit` | str | *(unlimited)* | Total download speed cap across all downloads combined, e.g. `500k`, `5M`, `1.5G` or plain bytes/sec. Values are bytes, not bits (`5M` = 5.0 MB/s). |
| `slow` | str | *(unset)* | Inter-title delay, e.g. `"20-40"`. |
| `best_available` | bool | `false` | Warn instead of failing when a requested resolution, range, video language, or audio language is missing, or when one DRM system does not license under `--all-drm`. The CLI flag is `--best-available`, also spelled `--warn-only`. |
| `skip_missing` | bool | `false` | Skip a title that misses a required language (`require_audio`, `require_video`, `require_subs`) or a track that a filter asks for, and continue with the next title. A run with one title, or a run that skips every title, still fails. The CLI flag is `--skip-missing`. For REST API jobs, set it in the [`serve`](services.md#serve) block. |
| `generate_dv` | bool | `false` | With `-r HYBRID`, make Dolby Vision from the HDR10+ metadata or the decoded video when a title has no DV track. With `-r HDR10` or `-r HDR10P`, make it for that base and ignore a DV track. See [Dolby Vision without a DV track](../../guide/downloading.md#dolby-vision-without-a-dv-track). |
| `proxy` | str | *(unset)* | Default proxy URI or 2-letter country. |
| `no_folder` | bool | `false` | Do not create a per-title folder. |
@@ -213,6 +213,10 @@ subs = [p for p in sys.argv[i].removeprefix("--subs=").split("\n") if p]
nothing about whether the season is complete on disk. Downloading a single episode fires
the post-script for that season as soon as the episode lands.
A title that track selection skips (`--skip-missing`, `-ado`, `-fso`) counts as done. The
`season` post-script still fires for the episodes that landed, so the folder can be missing
the skipped episodes. A run that skips every title fails, and then no `season` post-script fires.
A title that fails stops the whole run. unshackle dispatches the `failure` post-script for
that title and then returns. It downloads no further title, and no `season` or `run`
post-script fires after that point. This applies to every title type. If one episode of S01