feat(subtitles): add --sub-font and subtitle.ass_style for converted ASS

Converted subtitles get one style on a 1080-line canvas shaped like the video; native SSA/ASS is never restyled.
This commit is contained in:
imSp4rky
2026-11-11 10:04:57 -07:00
parent 590a57113b
commit f85d7f5fa8
11 changed files with 506 additions and 5 deletions
+1
View File
@@ -459,6 +459,7 @@ Make a download job. It requires `service` and `title_id`. Every other field is
| `forced_s_lang` | string[] | `[]` | Forced subtitle language(s); implies `forced_subs`. |
| `exact_lang` | boolean | `false` | A language tag without a region never falls back to another variant. A tag with a region is always exact. |
| `sub_format` | string | `null` | Output subtitle format. |
| `sub_font` | string | `null` | Font for subtitles converted to ASS; implies `sub_format` ASS. With another `sub_format` the request returns `400`. |
| `video_only` / `audio_only` / `subs_only` / `chapters_only` | boolean | `false` | Track-type restriction (at most one). |
| `no_subs` / `no_audio` / `no_chapters` / `no_video` / `no_attachments` | boolean | `false` | Skip a track type. |
| `skip_subtitle_errors` | boolean | `false` | Skip a subtitle that fails to download and keep the other tracks. |
+1
View File
@@ -107,6 +107,7 @@ unshackle dl [OPTIONS] SERVICE [SERVICE ARGS...]
| `-fsl`, `--forced-s-lang` | none | Language(s) wanted for forced subtitles; implies `-fs`. A `-` prefix excludes. |
| `--exact-lang` | off | A tag without a region selects only the variant it means and never falls back to another: `-l es` does not select `es-419`. A tag with a region, such as `es-419`, is always exact. 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. |
| `--sub-font` | - | Font for subtitles converted to ASS, with the rest of the look from `subtitle.ass_style`. Implies `--sub-format ASS` and overrides a `sub_format` from the configuration file; a typed `--sub-format` of another format is an error. |
!!! 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
+1
View File
@@ -1198,6 +1198,7 @@ authoritative list.
| `--forced-subs` | `-fs` | Include forced subtitles. |
| `--forced-s-lang` | `-fsl` | Forced subtitle language(s); implies `-fs`. `-` excludes. |
| `--sub-format` | | Output subtitle format. |
| `--sub-font` | | Font for subtitles converted to ASS; implies `--sub-format ASS`. |
| `--wanted` | `-w` | Episode/season range, an air date, or a track number for a music release (`1-5`, `2x3`). |
| `--daily` | | Fill missing air dates from TVDB during `--enrich`. |
| `--select-titles` | | Interactively pick episodes or films. |
+56
View File
@@ -83,6 +83,7 @@ Subtitle selection happens through `dl` flags. The most important is language se
| `-S`, `--subs-only` | Download only subtitle tracks. |
| `-ns`, `--no-subs` | Do not download subtitle tracks at all. |
| `--sub-format` | Output subtitle format / conversion target (see [conversion](#converting-subtitle-formats)). |
| `--sub-font` | Font for subtitles converted to ASS; implies `--sub-format ass` (see [custom font and style](#custom-font-and-style)). |
| `--skip-subtitle-errors` | Skip a subtitle that fails to download instead of aborting the whole title. |
### Choosing languages
@@ -404,6 +405,60 @@ The pattern is `{base}.{language}[.forced][.sdh].{extension}`. The
a format name like `srt` or `vtt`, or `original` to keep each subtitle in its downloaded
format.
## Custom font and style
Services mostly deliver WebVTT, which players draw in their own plain font. To use a font
of your choice, unshackle converts the subtitles to ASS and gives them one style:
```shell title="Convert to ASS in a custom font"
unshackle dl --sub-font "Noto Sans" EXAMPLE 81234567
```
`--sub-font` sets only the font name. The rest of the look comes from
[`subtitle.ass_style`](#subtitle-configuration), a mapping of style fields. A field takes its
[pysubs2 name](https://pysubs2.readthedocs.io/en/latest/api-reference.html#ssastyle-style-of-subtitles)
(`primarycolor`) or its ASS name (`PrimaryColour`). Give sizes and margins for a 1080-line
picture. unshackle writes a 1080-line canvas in the shape of the video, and the player scales
it to the screen, so the text has the same size on a 720p, 1080p or 2160p video.
```yaml title="unshackle.yaml"
subtitle:
ass_style:
fontname: Noto Sans
fontsize: 72
primarycolor: "&H00FFFFFF"
marginv: 60
```
The fields you leave out keep these defaults: `fontname: Arial`, `fontsize: 72`, `bold: true`,
`outline: 3.6`, `shadow: 1.5`, `backcolor: "&HA0000000"`, `marginl: 100`, `marginr: 100` and
`marginv: 60`. Write colours as `&HAABBGGRR` strings in quotes: in YAML, an unquoted `&` starts
an anchor, and an unquoted number is not read as a colour.
- A style makes ASS the output format when you set no `--sub-format`. With `--sub-format ass`
or `ssa` the style applies too. With another format, unshackle does not use the style.
A `--sub-font` that you type overrides a `sub_format` from the configuration file, and stops
the download with an error when you also type another `--sub-format`. A `sub_font` from the
configuration file is ignored when the format is not ASS.
- The style applies only to subtitles that unshackle converts into ASS. A subtitle that is
already SSA/ASS keeps its own styles, positions and canvas, and so does its SDH-stripped copy.
- The SDH-stripped copy of a converted track keeps the style.
- unshackle checks the style before it downloads. An unknown field or a value that does not
fit its field stops the download with an error.
- With `output_mode: sidecar` and a `sidecar_format` other than `ass`, the sidecar files do
not use the style.
- Closed captions that unshackle extracts from the video stay SRT.
!!! note "Player settings"
A player draws an ASS subtitle with its own style, so player options for text subtitles,
such as the mpv options `sub-font` and `sub-font-size`, no longer apply. The text size
follows the height of the video, so on a wide video such as 1920x800 the text is smaller
than on a 1920x1080 video.
The font must be on your system or in [`directories.fonts`](../reference/configuration/directories.md),
in a folder with the family name, for example `fonts/Noto Sans/NotoSans-Regular.ttf`. unshackle
attaches it to the MKV, as described below.
## Fonts for styled subtitles
Styled SSA/ASS subtitles reference specific fonts by name. When unshackle muxes such a
@@ -440,6 +495,7 @@ subtitle:
| `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. |
| `dedupe_codecs` | *(unset)* | a list of codecs, e.g. `[VTT, STPP, WVTT]` | Keep one copy of a subtitle that the service sends in several codecs: the earliest listed codec wins. |
| `ass_style` | *(unset)* | a mapping of pysubs2 style fields | The look of subtitles converted to ASS; makes ASS the default output format. See [custom font and style](#custom-font-and-style). |
### Which settings to change
+2
View File
@@ -41,6 +41,7 @@ Common config keys. This is a useful subset, and every `dl` flag works:
| `range` | list | `["SDR"]` | Colour range(s): `SDR`, `HLG`, `HDR10`, `HDR10P`, `DV`, `HYBRID`. |
| `channels` | float | *(unset)* | Audio channels, e.g. `6` for 5.1. |
| `sub_format` | str | *(unset)* | Convert subtitles to this format (`srt`, `vtt`, `original`, ...). |
| `sub_font` | str | *(unset)* | Font for subtitles converted to ASS; implies `sub_format: ass`. Ignored when `sub_format` is another format. |
| `forced_subs` | bool | `false` | Include forced subtitle tracks. |
| `forced_s_lang` | list | `[]` | Forced subtitle language(s); implies `forced_subs`. A `-` prefix excludes. |
| `forced_subs_only` | bool | `false` | Keep only forced subtitle tracks; implies `forced_subs`. |
@@ -194,6 +195,7 @@ in or writes them as sidecar files. See [Subtitles](../../guide/subtitles.md) fo
| `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. |
| `ass_style` | dict | *(unset)* | Style for subtitles converted to ASS, as pysubs2 or ASS style fields (`fontname`, `fontsize`, `primarycolor` or `PrimaryColour`, ...) for a 1080-line picture. Makes ASS the output format when `sub_format` is unset. See [Custom font and style](../../guide/subtitles.md#custom-font-and-style). |
| `dedupe_codecs` | list | *(unset)* | Subtitle codecs in order of preference, by value or name, for example `[VTT, STPP, WVTT]`. When tracks of the same language, forced/SDH/CC type and name come in several listed codecs, unshackle drops the ones in a later codec. It always keeps codecs you leave out. Unset keeps every track. |
```yaml