mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
A missing -sl language now warns and continues, with or without --best-available. --require-subs counts a forced track only when selection keeps it, and a title with no track left is skipped instead of failing at mux. BREAKING CHANGE: a missing -sl subtitle language no longer fails the run. Use --require-subs to fail on it.
507 lines
26 KiB
Markdown
507 lines
26 KiB
Markdown
# Subtitles
|
|
|
|
Subtitles are first-class tracks in unshackle. When you download a title, unshackle
|
|
parses the subtitle tracks from the manifest alongside video and audio. It downloads
|
|
them, cleans them up, and optionally converts them to the format you want. It then muxes
|
|
them into the final Matroska file, or writes them out as sidecar files next to it. The
|
|
sections below cover the supported formats, how unshackle normalizes tracks after
|
|
download, how to select and filter subtitles, and every subtitle-related configuration
|
|
option.
|
|
|
|
A handful of `dl` flags and a small `subtitle` block in your
|
|
[configuration file](../getting-started/configuration-file.md) control most of this. See
|
|
[Downloading](downloading.md) for how subtitle flags fit into a full download command.
|
|
|
|
## Supported formats
|
|
|
|
unshackle understands the following subtitle codecs. The **Format** column is the value
|
|
shown in track listings. The **Extension** column is the file extension used on disk.
|
|
|
|
| Codec (internal name) | Format | Extension | Notes |
|
|
| --- | --- | --- | --- |
|
|
| `SubRip` | SRT | `srt` | SubRip, the most widely compatible plain-text format |
|
|
| `SubStationAlpha` | SSA | `ssa` | SubStation Alpha (styled) |
|
|
| `SubStationAlphav4` | ASS | `ass` | Advanced SubStation Alpha (styled) |
|
|
| `TimedTextMarkupLang` | TTML | `ttml` | Timed Text Markup Language (IMSC/DFXP) |
|
|
| `WebVTT` | VTT | `vtt` | WebVTT |
|
|
| `SAMI` | SMI | `smi` | Synchronized Accessible Media Interchange |
|
|
| `MicroDVD` | SUB | `sub` | MicroDVD |
|
|
| `MPL2` | MPL2 | `mpl2` | MPL2 |
|
|
| `TMP` | TMP | `tmp` | TMP |
|
|
| `fTTML` | STPP | `stpp` | MPEG-DASH box-encapsulated TTML (IMSC1) |
|
|
| `fVTT` | WVTT | `wvtt` | MPEG-DASH box-encapsulated WebVTT |
|
|
|
|
!!! note "The two boxed formats convert automatically"
|
|
`fTTML` (STPP) and `fVTT` (WVTT) are the fragmented, MP4-box-encapsulated forms that
|
|
DASH and HLS deliver. You never keep these as-is: right after download they are
|
|
unwrapped into their plain counterparts (`fTTML` becomes TTML and `fVTT` becomes
|
|
WebVTT), so you always end up with an editable text subtitle. For a DASH track, the
|
|
unwrap also subtracts the manifest's `presentationTimeOffset` from the cue times, so the
|
|
subtitles stay in sync with the video. For an ISM track whose cues count from the start of
|
|
each fragment, the unwrap moves every cue onto the timeline of the track. Note that this is a
|
|
one-way, source-only relationship: you can convert *from* these boxed formats, but no
|
|
backend can write them as an output target, so they can never be a conversion
|
|
destination.
|
|
|
|
## What happens to a subtitle after download
|
|
|
|
A subtitle download does more than save a file. Once the bytes are on disk, unshackle runs
|
|
format-appropriate clean-up to make a single, valid file:
|
|
|
|
- **Encoding**: unshackle forces non-boxed subtitles to UTF-8, and unescapes stray
|
|
`‎` / `‏` bidirectional entities.
|
|
- **Segment merging**: when a WebVTT or boxed subtitle arrives as many DASH/HLS
|
|
segments, unshackle merges the segments back into one continuous document with
|
|
correct timing. A cue that a segment boundary cuts in two comes out as one cue.
|
|
- **WebVTT sanitization**: merged WebVTT runs through a repair pipeline that clamps
|
|
negative timestamps to `00:00:00.000`, removes stray cue identifiers (like `Q0`, `Q1`)
|
|
that confuse downstream parsers, and merges multi-line cues that a service split into
|
|
separate overlapping cues. By default unshackle keeps the original styling and layout
|
|
(see [`preserve_formatting`](#subtitle-configuration)).
|
|
- **Cue markup normalisation**: unshackle removes the WebVTT-only parts of a cue
|
|
payload, so no player draws them as dialogue. See
|
|
[Cue markup](#cue-markup-and-html-entities).
|
|
- **TTML for muxing**: Matroska cannot carry TTML. If you do not request a specific
|
|
output format, unshackle automatically converts any plain TTML track to WebVTT before
|
|
muxing (the closest lossless equivalent). Boxed `fTTML` is likewise unwrapped to TTML
|
|
first.
|
|
|
|
You normally do not need to think about any of this. It gives a clean file.
|
|
|
|
## Selecting and filtering subtitles
|
|
|
|
Subtitle selection happens through `dl` flags. The most important is language selection.
|
|
|
|
| Flag | Purpose |
|
|
| --- | --- |
|
|
| `-sl`, `--s-lang` | Language(s) wanted for subtitles. Defaults to `all`. A `-` prefix excludes, e.g. `all,-es`. |
|
|
| `--require-subs` | Require these languages to exist. The title fails if one is missing. `--s-lang` still decides what to keep. |
|
|
| `-fs`, `--forced-subs` | Include forced subtitle tracks (excluded by default). |
|
|
| `-fso`, `--forced-subs-only` | Keep only forced subtitle tracks and drop the other subtitles; implies `-fs`. |
|
|
| `-fsl`, `--forced-s-lang` | Language(s) wanted for forced subtitles; implies `-fs`. A `-` prefix excludes. |
|
|
| `--exact-lang` | A tag without a region never falls back to another variant: `es` does not match `es-419`. |
|
|
| `-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)). |
|
|
| `--skip-subtitle-errors` | Skip a subtitle that fails to download instead of aborting the whole title. |
|
|
|
|
### Choosing languages
|
|
|
|
By default (`--s-lang all`) unshackle keeps every subtitle language the service offers.
|
|
To narrow it down, pass a comma-separated list of language tags:
|
|
|
|
```shell title="English and Spanish subtitles only"
|
|
unshackle dl --s-lang en,es EXAMPLE 81234567
|
|
```
|
|
|
|
A tag without a region selects the variant it means, such as `en-US` for `en`, and every
|
|
regional form only when the title has no such variant. A tag with a region matches only
|
|
that variant:
|
|
|
|
```shell title="Only Latin-American Spanish, no other Spanish variants"
|
|
unshackle dl --s-lang es-419 EXAMPLE 'https://www.example.com/...'
|
|
```
|
|
|
|
If a requested language is missing, unshackle continues with the languages it found and
|
|
tells you which ones it dropped. To stop the title instead, use `--require-subs`. See
|
|
[exact vs fuzzy matching](downloading.md#exact-vs-fuzzy-matching) for the full rules.
|
|
|
|
### Requiring languages before downloading
|
|
|
|
`--require-subs` is a gate rather than a filter. If any language you name is missing, the
|
|
title fails, even under `--best-available`. It does not select tracks: `--s-lang` still
|
|
decides what to keep. This is useful for batch jobs where you want a title without your
|
|
must-have language to fail loudly, and not to silently give a partial result.
|
|
|
|
```shell title="Fail unless both English and French subs exist, then grab everything"
|
|
unshackle dl --require-subs en,fr EXAMPLE B0ABCDEFGH
|
|
```
|
|
|
|
### Forced subtitles
|
|
|
|
Forced tracks carry only the important on-screen signs and foreign-dialogue lines meant
|
|
to display even when you watch in your own language. They are **excluded by
|
|
default**. Add `-fs` / `--forced-subs` to include them:
|
|
|
|
```shell title="Include forced tracks alongside full subtitles"
|
|
unshackle dl --s-lang en --forced-subs EXAMPLE 81234567
|
|
```
|
|
|
|
`-fs` keeps forced tracks in every selected language. If you only want forced tracks
|
|
in certain languages, use `-fsl` / `--forced-s-lang` instead. It implies `-fs`, so
|
|
you do not need both:
|
|
|
|
```shell title="All full subtitles, but only the English forced track"
|
|
unshackle dl --s-lang all --forced-s-lang en EXAMPLE 81234567
|
|
```
|
|
|
|
If you pass both flags, `-fsl` wins and keeps only forced tracks in its languages.
|
|
It also understands `orig` (the title's original language) and `all` (every forced
|
|
track, same as plain `-fs`).
|
|
|
|
To download the forced tracks and no other subtitle, use `-fso` / `--forced-subs-only`.
|
|
It implies `-fs`, and `--s-lang` and `-fsl` still select the languages:
|
|
|
|
```shell title="Only the English forced track"
|
|
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.
|
|
|
|
## Track types: forced, SDH, and CC
|
|
|
|
Every subtitle track carries flags describing what kind of captions it holds. These
|
|
flags drive selection, sort order, output filenames, and the Matroska track flags.
|
|
|
|
- **Forced**: only signs and foreign dialogue, meant to play when the audio language
|
|
matches. Off by default. Enable it with `--forced-subs`.
|
|
- **SDH** (Subtitles for the Deaf and Hard-of-Hearing, also called HOH): a full
|
|
transcript that includes both dialogue and non-speech sound cues.
|
|
- **CC** (Closed Captions): captions originating from an EIA-608/708 broadcast stream,
|
|
often uppercase with `>>>` speaker markers and sound cues.
|
|
|
|
!!! note "SDH and CC are treated together for sorting and stripping"
|
|
unshackle orders subtitles from fewest to most captions: **Forced → Normal → SDH/CC**.
|
|
For hearing-impaired handling, unshackle groups SDH and CC tracks as the "most
|
|
captions" variant.
|
|
|
|
By default this type order is the primary sort, so all forced tracks come first, then
|
|
all normal tracks, then all SDH/CC tracks, each block sorted by language. To keep each
|
|
language next to its own variants instead, set
|
|
[`subtitle.group_by`](../reference/configuration/download.md#subtitle) to `language`. Use
|
|
[`subtitle.type_priority`](../reference/configuration/download.md#subtitle) to change the
|
|
Forced → Normal → SDH/CC order itself.
|
|
|
|
A single track cannot be both CC and SDH, and a forced track cannot also carry the CC or
|
|
SDH flag. unshackle rejects these combinations as invalid.
|
|
|
|
### Closed captions embedded in video
|
|
|
|
Some services do not ship subtitles as separate tracks but instead embed EIA-608/708
|
|
captions inside the video track. After unshackle downloads and decrypts a video track,
|
|
it probes the track for these captions and, when it finds them, extracts them into a SubRip
|
|
(`SRT`) subtitle flagged as CC. This runs automatically unless you disabled subtitles
|
|
(`--no-subs`) or video (`--no-video` / `--video-only` semantics), and it requires the
|
|
CCExtractor tool on your `PATH`.
|
|
|
|
## Automatic SDH stripping
|
|
|
|
When a language has an SDH track but **no** plain (non-SDH, non-forced) track, unshackle
|
|
can make a clean non-SDH version for you by stripping the hearing-impaired cues:
|
|
sound effects, speaker labels, music notes, and similar. This is on by default and
|
|
controlled by the [`strip_sdh`](#subtitle-configuration) config key.
|
|
|
|
The check uses the same [matching rules](downloading.md#exact-vs-fuzzy-matching) as
|
|
`--s-lang`. A plain track covers an SDH track only when it is the same variant: `en` and
|
|
`en-US` cover each other, but a plain `en-GB` track does not stop a stripped `en` or `en-US`
|
|
copy. When unshackle creates a stripped
|
|
copy, it keeps the original SDH track as well, so you end up with both.
|
|
|
|
The [`sdh_method`](#subtitle-configuration) config key selects the stripping engine
|
|
itself. The default (`auto`) prefers the `subby` cleaner for SRT, falls back to
|
|
SubtitleEdit when you install it, and otherwise uses the built-in `filter-subs`
|
|
approach.
|
|
|
|
Every engine removes the same three things: bracketed sound effects (`[MUSIC PLAYING]`,
|
|
`(door creaks)`), speaker labels (`MAN:`), and font tags. Every engine keeps plain
|
|
dialogue and inline `<i>` styling. They differ on music and lyric lines:
|
|
|
|
| `sdh_method` | Engine that runs | Lyric lines (`♪ ... ♪`) |
|
|
| --- | --- | --- |
|
|
| `auto` | `subby` for an SRT track. SubtitleEdit for any other format when you install it, otherwise `filter-subs`. | Kept, unless the fallback reaches `filter-subs`. |
|
|
| `subby` | `subby`, but only for a track that is already SRT. Any other format falls through to `filter-subs`. | Kept for SRT, removed otherwise. |
|
|
| `subtitleedit` | SubtitleEdit when you install it, otherwise `filter-subs`. | Kept with SubtitleEdit, removed without it. |
|
|
| `filter-subs` | `filter-subs` always. | Removed. |
|
|
|
|
Pick `filter-subs` when you want lyrics gone as well. Pick `subby` or install
|
|
SubtitleEdit when you want to read along with the songs.
|
|
|
|
!!! note "Why `convert_before_strip` exists"
|
|
`subby`'s SDH stripper only operates on SRT. Handed any other codec it returns
|
|
silently without stripping anything, not an error but a no-op that leaves the SDH
|
|
cues in place. This is the whole reason for [`convert_before_strip`](#subtitle-configuration):
|
|
non-SubtitleEdit engines need the subtitle converted to SRT first, or the
|
|
hearing-impaired cues stay in the file.
|
|
|
|
## Converting subtitle formats
|
|
|
|
By default unshackle keeps each subtitle in its downloaded format (only auto-converting
|
|
TTML to WebVTT for muxing, as noted above). To force a specific output format, use
|
|
`--sub-format`:
|
|
|
|
```shell title="Convert every subtitle to SRT"
|
|
unshackle dl --s-lang en,es --sub-format srt EXAMPLE 81234567
|
|
```
|
|
|
|
`--sub-format` accepts the common format names and aliases, including `srt`, `vtt`,
|
|
`ssa`, `ass`, and `ttml`. Pass `original` to explicitly keep the source format and skip
|
|
all conversion:
|
|
|
|
```shell title="Keep whatever the service delivered"
|
|
unshackle dl --sub-format original EXAMPLE 'https://www.example.com/...'
|
|
```
|
|
|
|
Conversion only runs when the source format differs from the target, so
|
|
`--sub-format srt` on a track that is already SRT is a no-op.
|
|
|
|
When unshackle converts SSA/ASS to a format that is not SSA/ASS, it writes the cues in
|
|
start-time order. SSA/ASS files often list layered sign events out of time order. The
|
|
SSA/ASS source file keeps its order, because the order of events on one layer sets the
|
|
order in which they are drawn.
|
|
|
|
!!! warning "Down-converting styled subtitles is lossy"
|
|
Converting styled SSA/ASS subtitles to SRT throws away positioning, colours, and
|
|
italics. unshackle never does this automatically. It only happens when you
|
|
explicitly ask for it with `--sub-format`. If you want to preserve styling, convert
|
|
to a format that supports it (or keep `original`).
|
|
|
|
Note that this automatic protection guards **only the default muxed track**: there
|
|
unshackle skips the conversion and keeps the styled original. It does **not** cover sidecars.
|
|
A `sidecar_format: srt` will still lossily flatten styled subtitles, dropping
|
|
positioning, colours, and styling, and a per-download `--sub-format srt` forces the
|
|
muxed track to convert as well. To keep raw styled sidecars, set
|
|
[`sidecar_format: original`](#subtitle-configuration).
|
|
|
|
### How the conversion backend is chosen
|
|
|
|
Under the hood unshackle picks the highest-fidelity converter that supports the
|
|
source→target pair. Which converter runs depends on the
|
|
[`conversion_method`](#subtitle-configuration) config key (default `auto`) and the
|
|
formats involved:
|
|
|
|
| Backend | Best at | Requires |
|
|
| --- | --- | --- |
|
|
| `subtitleedit` | Highest fidelity; preserves positioning and italics | SubtitleEdit installed |
|
|
| `subby` | Native SRT output with clean-up | - |
|
|
| `pysubs2` | Best fidelity for styled SSA/ASS | - (pure Python, always available) |
|
|
| `pycaption` | Last-resort fallback; flattens styling | - |
|
|
|
|
With `conversion_method: auto`, unshackle ranks these automatically per conversion.
|
|
Setting it to a specific value pins that backend as the first choice, falling back to
|
|
others only if the pin cannot convert the pair.
|
|
|
|
#### What each backend preserves
|
|
|
|
The table below comes from round-tripping a subtitle that carries italics, bold,
|
|
underline, positioning, and colour through every backend. Each cell shows the
|
|
styling that survives. `—` means that the backend cannot convert that pair.
|
|
|
|
| Conversion | `subtitleedit` | `pysubs2` | `subby` | `pycaption` |
|
|
| --- | --- | --- | --- | --- |
|
|
| WebVTT → SRT | italic, bold, underline, position | italic, underline | italic, position | *(strips all)* |
|
|
| WebVTT → ASS | italic, bold, underline, position | italic, bold, underline | — | — |
|
|
| WebVTT → TTML | italic, bold, underline, position | italic, bold, underline | position | *(strips all)* |
|
|
| ASS/SSA → SRT | all (+ colour) | italic, underline | — | — |
|
|
| ASS/SSA → TTML | position only | italic, bold, underline | — | — |
|
|
| ASS/SSA → VTT | position only | italic, underline | — | — |
|
|
|
|
Reading the table:
|
|
|
|
- **Keep the original**: leaving `--sub-format` unset (or set to `original`) never
|
|
round-trips the file, so every style survives. Only convert when your player cannot
|
|
read the source format.
|
|
- **`subtitleedit`** (SubtitleEdit / `seconv`): the best choice for anything → SRT. It
|
|
is the only backend that carries colour, and it embeds `{\an8}` tags to preserve
|
|
positioning. When writing TTML or WebVTT from ASS it flattens inline styling to plain
|
|
text and keeps only positioning, so avoid it there.
|
|
- **`pysubs2`**: keeps inline italic/underline on every pair, and bold except when
|
|
writing SRT or WebVTT. It never carries positioning or colour. SSA/ASS is its native
|
|
model, which makes it the best pick for SSA↔ASS.
|
|
- **`subby`**: reads only WebVTT/fVTT/SAMI (never ASS), and it is best for → SRT, where
|
|
it uniquely converts WebVTT cue settings into `{\an8}` positioning. Its
|
|
`CommonIssuesFixer` may also drop near-duplicate cues.
|
|
- **`pycaption`**: removes all styling. Last-resort fallback only.
|
|
|
|
!!! tip "What `auto` picks"
|
|
`auto` prefers `subtitleedit` first when you install it, then falls back to `subby`
|
|
for → SRT and `pysubs2` otherwise. The one exception comes from the table above:
|
|
for ASS/SSA → TTML or WebVTT it picks `pysubs2` even when SubtitleEdit is
|
|
installed, since SubtitleEdit flattens inline styling on those pairs. Most installs
|
|
do not ship SubtitleEdit, so in practice `auto` means `subby` (→ SRT) or `pysubs2`.
|
|
Install [SubtitleEdit / `seconv`](#subtitle-configuration) if you need colour or the
|
|
highest → SRT fidelity.
|
|
|
|
### Cue markup and HTML entities
|
|
|
|
Styling is not the only thing a conversion has to handle. WebVTT carries markup that no
|
|
other format understands, and a player that does not know a tag draws it as dialogue.
|
|
unshackle removes that markup before any backend runs, so the result is the same for
|
|
every `conversion_method`:
|
|
|
|
| WebVTT markup | Result |
|
|
| --- | --- |
|
|
| `<c.bg_transparent>text</c>` | Tags removed, text kept. A class only selects a CSS `::cue` rule that no player ever receives, so it carries no styling. |
|
|
| `<i.loud>`, `<b.x>`, `<u.y>` | Class list removed, the styling tag kept. |
|
|
| `<v Bob>`, `<lang ja>`, `<ruby>`, `<rt>` | Tags removed, text kept. |
|
|
| `<00:00:02.000>` | Removed. These are karaoke timings that no subtitle player renders. |
|
|
| `&`, ` `, `'` | Decoded to real characters for SRT, SSA, and ASS output. Kept escaped for WebVTT and TTML, which need them. |
|
|
|
|
!!! warning "A class list breaks more than you expect"
|
|
FFmpeg reads SubRip markup as a tag only while the tag name stays inside
|
|
`[0-9a-zA-Z_/]`. The dot in `<i.loud>` fails that test, so FFmpeg prints the opening
|
|
tag on screen and then closes an italic run that never opened. The FFmpeg WebVTT
|
|
reader matches `i`, `b`, and `u` exactly, so there the same class silently loses the
|
|
styling. mpv, VLC, Plex, and Jellyfin all use one of those two readers.
|
|
|
|
!!! note "Config wins over a service's preference"
|
|
A service may set a `preferred_conversion_method` on its own tracks (for example when
|
|
it ships subtitles that a particular backend handles best). An explicit
|
|
`conversion_method` in `unshackle.yaml` always overrides that per-track preference, so
|
|
your config takes precedence. This only matters when a service ships a non-default
|
|
preference. Leave `conversion_method` at `auto` to let the service's hint stand.
|
|
|
|
## Muxed vs. sidecar output
|
|
|
|
By default unshackle muxes subtitles **into** the final `.mkv`. You can instead (or
|
|
additionally) write them as separate sidecar files next to the video, controlled by the
|
|
[`output_mode`](#subtitle-configuration) config key:
|
|
|
|
=== "mux (default)"
|
|
|
|
```yaml title="unshackle.yaml"
|
|
subtitle:
|
|
output_mode: mux
|
|
```
|
|
|
|
unshackle embeds the subtitles in the Matroska file. It writes nothing separately.
|
|
|
|
=== "sidecar"
|
|
|
|
```yaml title="unshackle.yaml"
|
|
subtitle:
|
|
output_mode: sidecar
|
|
sidecar_format: srt
|
|
```
|
|
|
|
unshackle writes the subtitles as standalone files and does **not** mux them into
|
|
the video (when there is a video or audio track to sit beside).
|
|
|
|
=== "both"
|
|
|
|
```yaml title="unshackle.yaml"
|
|
subtitle:
|
|
output_mode: both
|
|
sidecar_format: srt
|
|
```
|
|
|
|
unshackle muxes the subtitles into the video **and** writes them as sidecar files.
|
|
|
|
unshackle names sidecar files after the output file, with the language and flags encoded
|
|
in the name, so players and media managers can match them automatically:
|
|
|
|
```text
|
|
Show.S01E01.1080p.WEB.en.srt
|
|
Show.S01E01.1080p.WEB.en.forced.srt
|
|
Show.S01E01.1080p.WEB.en.sdh.srt
|
|
```
|
|
|
|
The pattern is `{base}.{language}[.forced][.sdh].{extension}`. The
|
|
[`sidecar_format`](#subtitle-configuration) config key sets the sidecar file format. Use
|
|
a format name like `srt` or `vtt`, or `original` to keep each subtitle in its downloaded
|
|
format.
|
|
|
|
## Fonts for styled subtitles
|
|
|
|
Styled SSA/ASS subtitles reference specific fonts by name. When unshackle muxes such a
|
|
subtitle it scans it for the fonts it uses and attaches the matching font files to the
|
|
Matroska output so the styling renders correctly on playback. If a referenced font is
|
|
not available locally, unshackle logs the missing font names and suggests installing the
|
|
relevant font packages.
|
|
|
|
## Subtitle configuration
|
|
|
|
You can set defaults for all of the behaviour above in the `subtitle` block of your
|
|
[configuration file](../getting-started/configuration-file.md):
|
|
|
|
```yaml title="unshackle.yaml"
|
|
subtitle:
|
|
strip_sdh: true
|
|
preserve_formatting: true
|
|
conversion_method: auto
|
|
sdh_method: auto
|
|
convert_before_strip: true
|
|
output_mode: mux
|
|
sidecar_format: srt
|
|
```
|
|
|
|
| Key | Default | Values | Purpose |
|
|
| --- | --- | --- | --- |
|
|
| `strip_sdh` | `true` | `true` / `false` | Auto-generate a stripped non-SDH copy when a language has only an SDH track. |
|
|
| `preserve_formatting` | `true` | `true` / `false` | Keep original WebVTT styling and layout during sanitization. When `false`, WebVTT is re-encoded more aggressively (merging identical cues, dropping empty ones). |
|
|
| `conversion_method` | `auto` | `auto`, `subby`, `pysubs2`, `subtitleedit`, `pycaption` | Which conversion backend to prefer. `auto` ranks them per conversion. |
|
|
| `sdh_method` | `auto` | `auto`, `subby`, `subtitleedit`, `filter-subs` | Engine used to strip hearing-impaired cues. |
|
|
| `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. |
|
|
| `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. |
|
|
|
|
### Which settings to change
|
|
|
|
The defaults give a clean, player-safe file, so most people change nothing. Start here
|
|
only when you want something the defaults do not do:
|
|
|
|
| If you want | Set |
|
|
| --- | --- |
|
|
| Every original style kept, with no re-encode at all | `--sub-format original`, and `sidecar_format: original` for sidecars |
|
|
| The highest conversion fidelity, including colour | Install SubtitleEdit. `conversion_method` stays `auto` |
|
|
| Music and lyric lines stripped along with the sound effects | `sdh_method: filter-subs` |
|
|
| Lyric lines kept | `sdh_method: subby`, or install SubtitleEdit |
|
|
| No stripped copies made at all | `strip_sdh: false` |
|
|
| One copy of each subtitle when the service sends the same one in several codecs | `dedupe_codecs: [VTT, STPP, WVTT]` |
|
|
| One backend for every conversion, whatever you install | `conversion_method: <backend>` |
|
|
|
|
Two keys read as if they control markup, and do not:
|
|
|
|
- **`preserve_formatting`** decides whether unshackle re-encodes a WebVTT file after
|
|
the repair pipeline. Left `true`, the cue text passes through as written. Set to
|
|
`false`, unshackle re-encodes the file through pycaption, which merges identical cues
|
|
and drops empty ones but also flattens styling. Neither setting leaves WebVTT-only
|
|
markup in the file: [cue markup normalisation](#cue-markup-and-html-entities) runs
|
|
either way.
|
|
- **`convert_before_strip`** applies to SDH stripping, not to your output format. It
|
|
converts the working copy to SRT so a non-SubtitleEdit engine can read it. Turning it
|
|
off makes `subby` and `filter-subs` no-ops on anything that is not already SRT.
|
|
|
|
!!! tip "SubtitleEdit is optional but recommended"
|
|
Several high-fidelity paths (the best-quality conversions and the SubtitleEdit SDH
|
|
stripper) only activate when you install the SubtitleEdit CLI and put it on your `PATH`.
|
|
See [Installation](../getting-started/installation.md) for how to add it. Without it,
|
|
unshackle falls back to its built-in Python backends.
|
|
|
|
---
|
|
|
|
## Developer reference
|
|
|
|
!!! note "Audience"
|
|
This section is for people extending unshackle or writing services. Everyday users
|
|
can stop here.
|
|
|
|
The subtitle model lives in `unshackle.core.tracks.Subtitle`. A few internals worth
|
|
knowing when working with subtitle tracks in code:
|
|
|
|
- **Flags and validation**: `Subtitle(codec=..., cc=..., sdh=..., forced=...)`. A track
|
|
cannot be both `cc` and `sdh`, and you cannot combine `forced` with either. Both raise
|
|
`ValueError` at construction.
|
|
- **`convert(codec, *, forced=False)`**: converts the downloaded file in place through
|
|
the backend chain in `subtitle_convert.run_conversion`. `forced=True` (what
|
|
`--sub-format` sets) is the only way to permit lossy styled down-converts such as
|
|
ASS→SRT. Raises `NotImplementedError` if no backend supports the pair and
|
|
`RuntimeError` if all attempts fail.
|
|
- **`strip_hearing_impaired()`**: removes SDH cues using the engine selected by
|
|
`config.subtitle["sdh_method"]`.
|
|
- **`reverse_rtl()`**: fixes right-to-left sentence-ending positioning with SubtitleEdit
|
|
(requires the binary).
|
|
- **`parse(data, codec)`**: the central parser, returning a `pycaption.CaptionSet`. It
|
|
handles the boxed DASH formats, SAMI, and the WebVTT repair helpers.
|
|
- **`extract_fonts(text)`**: returns the set of font names referenced by an ASS/SSA
|
|
subtitle, used to decide which fonts to attach at mux time.
|
|
|
|
The conversion backends (`SubtitleEditBackend`, `SubbyBackend`, `Pysubs2Backend`,
|
|
`PycaptionBackend`) each have a small protocol (`is_available()`, `can_convert()`,
|
|
`rank()`, `convert()`), and unshackle tries them in rank order, lowest rank first. See
|
|
[the Tracks API reference](../reference/api/tracks.md) for the full model.
|