Summary
ignore_forced_subtitles is honoured for embedded subtitle streams but not for external subtitle files. A forced sidecar such as Movie.en.forced.srt satisfies the English coverage check, so the file is skipped and there is no way to obtain a full subtitle for it.
Observed on 2026.08.1, but the code path looks unchanged for a long time.
Why this looks like a bug rather than a choice
has_internal_subtitle_in_language deliberately excludes forced tracks, and its docstring states the rationale:
Forced subtitle tracks are excluded when ignore_forced_subtitles is enabled, because a forced track only covers a small portion of dialogue and should not be treated as full subtitle coverage.
That reasoning is about what a forced subtitle contains, not about where it is stored, so it applies identically to a sidecar. But has_external_subtitle_in_language has no equivalent screen.
Line references on 2026.08.1:
subtitle_exists_in_language line 2071, which ORs internal and external
has_internal_subtitle_in_language line 2091, forced excluded at line 2116 (if ignore_forced_subtitles and is_forced: continue)
has_external_subtitle_in_language line 2128, filename split at line 2160, language match at line 2174, with no forced handling anywhere between
Reproduction
Blue Planet II S01E01.mkv
Blue Planet II S01E01.en.forced.srt <- forced only, no full English sub
subtitle_exists_in_language(video, LanguageCode.ENGLISH) returns True with IGNORE_FORCED_SUBTITLES=True, so the file is skipped. Move the same forced subtitle into the container as an embedded forced track and it correctly returns False.
At line 2160 the filename is split on ., so Movie.en.forced yields ['en', 'forced'], is_valid_subtitle_language sees en, and the file matches. Note this is independent of naming order: Movie.forced.en yields ['forced', 'en'] and matches just the same, so both common conventions are affected.
Suggested fix
Screen the parts for a forced marker before the language match, mirroring what the embedded path already does:
if ignore_forced_subtitles and any(part.lower() == "forced" for part in subtitle_parts):
logging.debug(f"Skipping forced subtitle file {file_name} for {video_file}")
continue
Checking the parts list rather than the raw filename keeps it order-agnostic and avoids matching a title that happens to contain the word.
Impact
Any workflow that produces forced-only sidecars is affected. In our case it is self-inflicted in an obvious way: the tool generates .en.forced.srt for foreign-dialogue segments and then subgen declines to produce the full subtitle, having counted that sidecar as coverage.
One behaviour note if you take this: installs whose only subtitle for a file is a forced sidecar will start transcribing those files rather than skipping them. That is the intended semantics and matches embedded behaviour, but it will produce new work on affected libraries, so it may be worth a release note.
Downstream status
We have patched this in our fork (coaxk/subarr-subgen v4.24) so our users are unblocked, and we would rather drop the patch than carry it. Happy to open a PR against upstream if the approach above looks right to you.
Reported to us as coaxk/subarr#483.
Summary
ignore_forced_subtitlesis honoured for embedded subtitle streams but not for external subtitle files. A forced sidecar such asMovie.en.forced.srtsatisfies the English coverage check, so the file is skipped and there is no way to obtain a full subtitle for it.Observed on
2026.08.1, but the code path looks unchanged for a long time.Why this looks like a bug rather than a choice
has_internal_subtitle_in_languagedeliberately excludes forced tracks, and its docstring states the rationale:That reasoning is about what a forced subtitle contains, not about where it is stored, so it applies identically to a sidecar. But
has_external_subtitle_in_languagehas no equivalent screen.Line references on
2026.08.1:subtitle_exists_in_languageline 2071, which ORs internal and externalhas_internal_subtitle_in_languageline 2091, forced excluded at line 2116 (if ignore_forced_subtitles and is_forced: continue)has_external_subtitle_in_languageline 2128, filename split at line 2160, language match at line 2174, with no forced handling anywhere betweenReproduction
subtitle_exists_in_language(video, LanguageCode.ENGLISH)returnsTruewithIGNORE_FORCED_SUBTITLES=True, so the file is skipped. Move the same forced subtitle into the container as an embedded forced track and it correctly returnsFalse.At line 2160 the filename is split on
., soMovie.en.forcedyields['en', 'forced'],is_valid_subtitle_languageseesen, and the file matches. Note this is independent of naming order:Movie.forced.enyields['forced', 'en']and matches just the same, so both common conventions are affected.Suggested fix
Screen the parts for a forced marker before the language match, mirroring what the embedded path already does:
Checking the parts list rather than the raw filename keeps it order-agnostic and avoids matching a title that happens to contain the word.
Impact
Any workflow that produces forced-only sidecars is affected. In our case it is self-inflicted in an obvious way: the tool generates
.en.forced.srtfor foreign-dialogue segments and then subgen declines to produce the full subtitle, having counted that sidecar as coverage.One behaviour note if you take this: installs whose only subtitle for a file is a forced sidecar will start transcribing those files rather than skipping them. That is the intended semantics and matches embedded behaviour, but it will produce new work on affected libraries, so it may be worth a release note.
Downstream status
We have patched this in our fork (coaxk/subarr-subgen v4.24) so our users are unblocked, and we would rather drop the patch than carry it. Happy to open a PR against upstream if the approach above looks right to you.
Reported to us as coaxk/subarr#483.