feat(tracks): add language_priority to sort preferred languages first

subtitle.language_priority and audio.language_priority prepend to the CLI
languages, so the named languages sort first and nothing is filtered.
--exact-lang now applies to the sorts too, through matching_languages, which
follows the same RFC 4647 rule as Tracks.by_language and ignores invalid tags.
This commit is contained in:
imSp4rky
2026-08-03 10:15:05 -06:00
parent 4e1ccf3230
commit a6997f6bca
10 changed files with 277 additions and 36 deletions
+7 -1
View File
@@ -97,9 +97,15 @@ unshackle dl [OPTIONS] SERVICE [SERVICE ARGS...]
| `--require-subs` | - | Required subtitle langs; keeps **all** subs only if these exist. **Cannot combine with `--s-lang`.** |
| `-fs`, `--forced-subs` | off | Include forced subtitle tracks. |
| `-fsl`, `--forced-s-lang` | none | Language(s) wanted for forced subtitles; implies `-fs`. |
| `--exact-lang` | off | Exact matching only: `-l es-419` matches `es-419`, not `es-ES`. |
| `--exact-lang` | off | Exact matching only: `-l es-419` matches `es-419`, not `es-ES`. Applies to selection and to sort order. |
| `--sub-format` | - | Output subtitle format (`SRT`/`srt`, `VTT`/`webvtt`, `ASS`/`ssa`, `TTML`, `SMI`, ...), or `original` to keep the source format. |
!!! tip "These flags select languages, they do not order them"
Naming languages with `-l` or `-sl` removes the others. To keep every language but put
your preferred ones first, use `audio.language_priority` and
[`subtitle.language_priority`](../reference/configuration.md#subtitle) in your
configuration file.
The special language tokens `orig`, `all`, and `best` are honoured everywhere a language is expected.
### Title & episode selection
+20
View File
@@ -254,6 +254,26 @@ it is used. You can override each stream type independently:
unshackle dl -al en EXAMPLE 81234567
```
### Sort order
`-l` and `-sl` **select** languages: naming some removes the rest. To keep every language
but decide which ones come first, set a priority list in your configuration file:
```yaml title="unshackle.yaml"
audio:
language_priority: [orig, en]
subtitle:
group_by: language
language_priority: [en, es, fr]
```
Subtitles then start with English, Spanish, and French. The languages you leave out follow
alphabetically, after the title's original language. Nothing is removed. Audio works the
same way, but the languages you leave out keep their bitrate and codec order instead of an
alphabetical one.
See [`subtitle.language_priority`](../reference/configuration.md#subtitle) for the details.
### Exact vs fuzzy matching
By default language matching is fuzzy: `-l en` also accepts `en-US`, `en-GB`, etc. (up
+2
View File
@@ -376,6 +376,8 @@ subtitle:
| `convert_before_strip` | `true` | `true` / `false` | Convert a subtitle to SRT before stripping SDH (for non-SubtitleEdit engines). |
| `output_mode` | `mux` | `mux`, `sidecar`, `both` | Whether subtitles are embedded, written as sidecars, or both. |
| `sidecar_format` | `srt` | a format name (e.g. `srt`, `vtt`) or `original` | Format used for sidecar files. |
| `group_by` | `type` | `type`, `language` | Whether all tracks of one type stay together, or each language stays next to its own variants. |
| `language_priority` | *(unset)* | a list of languages | Languages to sort to the top. The rest keep the default order. Nothing is removed. |
!!! tip "SubtitleEdit is optional but recommended"
Several high-fidelity paths (the best-quality conversions and the SubtitleEdit SDH
+33 -1
View File
@@ -378,6 +378,7 @@ written as sidecar files. See [Subtitles](../guide/subtitles.md) for the full gu
| `sidecar_format` | str | `"srt"` | Format for sidecar files when `output_mode: sidecar`. |
| `type_priority` | list | *(unset)* | Ordered ranking of subtitle types (`forced`, `normal`, `sdh`; CC counts as SDH). Types you leave out fall to the end. |
| `group_by` | str | `"type"` | Which key groups the subtitle tracks. `type` puts all forced tracks together, then all normal, then all SDH. `language` keeps each language next to its own variants. |
| `language_priority` | list | *(unset)* | Languages to sort to the top, in the order you give. The rest keep the default order: original language first, then alphabetical. Nothing is removed. |
```yaml
subtitle:
@@ -405,6 +406,33 @@ subtitle:
`type_priority` still sets the order of the variants, so with `group_by: language` it
controls the order inside each language.
!!! note "`language_priority` sorts languages, it does not select them"
By default the title's original language comes first and every other language follows
alphabetically. For an English title that gives English, Arabic, Bulgarian, and the
rest. Give `language_priority` the languages you want first:
```yaml
subtitle:
group_by: language
language_priority: [en, es, fr]
```
| | Result for an English title |
|---|---|
| Default | English, Arabic, Bulgarian, Spanish, French, Japanese |
| With the list above | English, Spanish, French, Arabic, Bulgarian, Japanese |
This is only an order. Every other language is still downloaded, which is what makes
it different from `-sl`: `-sl en,es,fr` **removes** all other subtitles.
Use `orig` in the list for the title's original language. A base tag also covers its
regional forms, so `en` sorts `en-US` and `en-GB`, but `zh` does **not** cover
`zh-Hant`, which you must name. The sort ignores entries that match no track, and it
ignores entries that are not valid language tags.
`--exact-lang` applies here too: with it, `en` sorts only `en`, and `en-US` falls to
the alphabetical tail with the rest. Name each tag you want when you use that flag.
!!! note "`type_priority` also picks the default subtitle"
At mux time the first track in the default subtitle language gets the `default` flag.
The built-in order is `forced`, `normal`, `sdh`, which means a forced track becomes
@@ -421,13 +449,17 @@ subtitle:
- **Type:** `dict`  ·  **Default:** `{}`
Audio handling options. The most notable sub-key is **`codec_priority`**, an ordered list of
audio codecs used to break ties in track selection.
audio codecs used to break ties in track selection. **`language_priority`** is an ordered list
of languages that sort to the top of the audio tracks.
```yaml
audio:
codec_priority:
- EAC3
- AC3
language_priority:
- orig
- en
```
!!! note "`codec_priority` is a soft priority: nothing is dropped"