Skip to content

fix(aniworld/torznab): restore English Sub/Dub releases (language decoder + specials-mapping guard)#132

Open
Solid-Shop wants to merge 2 commits into
Zzackllack:mainfrom
Solid-Shop:fix/aniworld-english-language-discovery
Open

fix(aniworld/torznab): restore English Sub/Dub releases (language decoder + specials-mapping guard)#132
Solid-Shop wants to merge 2 commits into
Zzackllack:mainfrom
Solid-Shop:fix/aniworld-english-language-discovery

Conversation

@Solid-Shop

@Solid-Shop Solid-Shop commented Jun 14, 2026

Copy link
Copy Markdown

Summary

Restores English Sub/Dub anime releases that stopped appearing in Sonarr. Two independent root causes are fixed, plus a defaults safety net. Verified live against a dev stack (aniworld v4, aniworld==4.2.1).

1. Language decoder silently dropped English variants

For aniworld.to, EpisodeCompat.available_languages mapped each (Audio, Subtitles) key only through the upstream INVERSE_LANG_KEY_MAP / LANG_LABELS, and continued on KeyError — silently dropping any English key the maps didn't recognise, so only German reached Sonarr. The s.to branch had a parallel bug: it only emitted Dub variants (subtitles == "None"), dropping all Sub variants.

Fix: a shared _label_from_lang_tuple() helper decodes the tuple directly into the canonical labels (German/English Dub/Sub). aniworld falls back to it on KeyError; s.to reuses it; _normalize_language_for_backend gets a matching inverse fallback so a recovered label still resolves for download. "English Dub" added to the aniworld defaults as a safety net.

2. Specials-mapping hijacked real episodes

Episode searches probe languages in the default order, which starts with German Dub. For an episode that lacks German Dub (sub-only), the first probe "failed", the code assumed the episode was a special, and remapped the real episode (e.g. S4E10) to a season-0 film (S0E2) that was German-only — burying the real episode's English Sub.

Fix: before remapping to a special, check whether the real requested episode advertises any languages; if it does, treat it as a normal episode and skip special-mapping. Genuine specials (episodes with no normal page) still map.

Tests

  • test_aniworld_available_languages_recovers_unmapped_english
  • test_sto_available_languages_includes_subtitle_variants

cd apps/api && uv run pytest tests/unit/core/downloader/test_episode.py

Live verification (dev stack)

  • aniworld S4E10 feed now emits GER.SUB and ENG.SUB for the real episode (magnet aw_s=4&aw_e=10); log shows Skipping special mapping ... treating as a normal episode.
  • Arifureta S2E11 (has English on AniWorld) emits ENG.SUB; S2E12 is correctly German-only (verified against the raw AniWorld page HTML — episode 12 has no English key yet).

Out of scope (deferred)

  • Cross-site routing: a romaji query can resolve to s.to, which may lack Sub variants for a title.
  • One video host per release is intentional (automatic fallback across hosts happens at download time).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • English Dub is now available as a default option for aniworld.to content
  • Bug Fixes

    • Improved language variant detection for unmapped audio and subtitle combinations, ensuring variants like English Sub and German Sub are properly recognized
    • Enhanced special episode mapping with more intelligent fallback detection
  • Tests

    • Added test coverage for language variant recovery and subtitle inclusion across multiple providers

Solid-Shop and others added 2 commits June 13, 2026 21:13
…Sub variants for s.to)

English Sub and English Dub releases stopped appearing in Sonarr after a recent
update; only German Sub/German Dub (via VOE) showed up, even though the English
variants exist on the AniWorld site.

Root cause
----------
With TORZNAB_SEASON_SEARCH_MODE defaulting to "fast", the languages emitted to
Sonarr come directly from EpisodeCompat.available_languages (no host/provider
resolution is involved in fast-mode discovery, so the providers-hosters refactor
is not the cause). For aniworld.to, that property mapped each (Audio, Subtitles)
key ONLY through the pinned aniworld==4.2.1 library:

    label = LANG_LABELS[INVERSE_LANG_KEY_MAP[key]]

and on KeyError did `continue`, silently dropping the key. When AniWorld's
English language keys are absent from that library map (site/library drift, or
fallout from the aniworld v3->v4 migration), English Sub/Dub get dropped at
discovery while German keys still map -- so only German reaches Sonarr.

The s.to branch of the same property had a parallel latent bug: it only emitted
a label when subtitles == "None", returning German Dub / English Dub only and
dropping every subtitle variant (German Sub, English Sub).

Fix
---
- Add a shared module-level helper `_label_from_lang_tuple(key)` that decodes an
  aniworld>=4 (Audio, Subtitles) key into the four canonical labels used
  everywhere else (German Dub / English Dub / German Sub / English Sub). It is
  defensive about enum `.value` members as well as raw strings/ints: no subtitle
  overlay -> the audio defines a "Dub"; a subtitle overlay -> the subtitle
  language defines a "Sub".
- aniworld.to available_languages: still prefer the upstream label maps, but on
  KeyError fall back to the helper instead of dropping the key. Only log-skip
  when the helper also cannot map the key.
- s.to available_languages: reuse the same helper so German Sub / English Sub
  are emitted, not just the Dub variants.
- _normalize_language_for_backend: add a matching inverse fallback so a variant
  surfaced only via the helper can still be resolved for download. When the
  upstream INVERSE_LANG_LABELS lookup misses, scan the raw provider data for the
  (Audio, Subtitles) tuple whose decoded label matches and return it directly;
  _get_provider_redirect_url already matches raw tuples by value.
- config.py: add "English Dub" to the aniworld.to default_languages as a
  low-risk safety net (in fast mode a default is only emitted when also in the
  discovered set, so this does not create phantom releases).

Tests
-----
- test_aniworld_available_languages_recovers_unmapped_english: AniWorld serves
  four variants where only "German Dub" is in the upstream maps; assert all four
  (incl. English) are recovered and that get_direct_link resolves a variant known
  only via the fallback.
- test_sto_available_languages_includes_subtitle_variants: assert s.to now emits
  German Sub / English Sub.

Note: stale EpisodeAvailability rows recorded while English was unavailable only
affect strict mode (they expire via AVAILABILITY_TTL_HOURS or on re-probe); fast
mode is unaffected because discovery sets availability directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A regular episode whose first probed language was unavailable was being
remapped to a season-0 film/special, which buried the episode's real
language variants.

Symptom: searching "That Time I Got Reincarnated as a Slime" S4E10 returned
only German (from the S0E2 film) and never English Sub, even though the real
aniworld.to S4E10 advertises ['English Sub', 'German Sub'].

Cause: episode-search probes candidate languages in the default order, which
starts with "German Dub". S4E10 has no German Dub, so the first probe missed;
emit_tvsearch_episode_items then attempted special-mapping (because the
language was "not available") and remapped S4E10 -> S0E2 (a German-only film).
Every subsequent language was then probed against the film, so English Sub on
the real episode was never emitted.

Fix: before remapping to a special, check whether the real requested episode
actually advertises any languages (via the existing fast-season discovery
helper). If it does, treat it as a normal episode and skip special-mapping;
only remap when the real episode has no normal page (no advertised languages),
which is the genuine-special case the feature was built for.

Verified live (dev stack): the S4E10 Torznab feed now emits both
GER.SUB and ENG.SUB for the real S04E10 (magnet aw_s=4&aw_e=10), and the log
shows "Skipping special mapping ... treating as a normal episode."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Solid-Shop
Solid-Shop requested a review from Zzackllack as a code owner June 14, 2026 15:55
@coderabbitai

This comment was marked as resolved.

@coderabbitai coderabbitai Bot added the bug Something isn't working label Jun 14, 2026
@Zzackllack

Copy link
Copy Markdown
Owner

Thanks for your contribution; I am gonna take a look later...

@Zzackllack Zzackllack left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution.
I’m requesting changes because the new special-mapping guard treats failed or unknown language discovery as proof that an episode is missing.
Additionally, the claimed AniWorld language-mapping root cause is not reproduced against the repository’s pinned dependency.
The inline comments contain the specific required changes.

# special when the real requested episode advertises no
# languages at all (i.e. it has no normal page).
real_languages = (
discover_episode_languages_for_fast_season_mode_fn(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discover_episode_languages_for_fast_season_mode_fn() returns [] both when
the episode genuinely has no languages and whenever discovery fails. Its
implementation catches import errors, build_episode() failures, provider
failures, and malformed/empty metadata and collapses all of those states to
[]. This branch therefore still performs the special remap during transient
AniWorld/network/parser failures and can hijack a normal episode exactly as
before. The comment's claim that no languages means "it has no normal page" is
not true for this API.

Please use a result that distinguishes found with languages, confirmed missing, and discovery failed/unknown. Special mapping should only happen
for a confirmed missing episode. At minimum, add regression coverage proving a
discovery failure cannot remap a normal episode.

@@ -502,26 +502,55 @@ def emit_tvsearch_episode_items(
):
if not special_map_attempted:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the riskiest behavior change in the PR, but neither new test exercises
it. Please add Torznab regression tests that prove:

  1. A real episode with German Dub unavailable but another advertised
    language is not remapped.
  2. A genuine special still maps.
  3. A failed/unknown language-discovery request does not cause a normal episode
    to be remapped.
  4. The discovery helper is called at most once per request.

The existing specials tests pass, but they do not validate the newly added
branch or its failure semantics.

)


def test_aniworld_available_languages_recovers_unmapped_english(monkeypatch):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test fabricates an upstream config where only German Dub exists in the
maps, but the dependency pinned by this repository (aniworld==4.2.1) already
maps all four variants:

  • German Dub
  • English Sub
  • German Sub
  • English Dub

I verified the installed pinned package's LANG_KEY_MAP, LANG_LABELS,
INVERSE_LANG_KEY_MAP, and INVERSE_LANG_LABELS; all four are present.
Therefore this test demonstrates that the fallback works under a hypothetical
future drift scenario, but it does not reproduce the stated current root
cause. Please provide the actual raw key that fails with 4.2.1/live AniWorld,
or reframe this as defensive hardening and identify the real cause of the
reported missing English results.

Comment thread apps/api/app/config.py
"alphabet_url": ANIWORLD_ALPHABET_URL,
"titles_refresh_hours": ANIWORLD_TITLES_REFRESH_HOURS,
"default_languages": ["German Dub", "German Sub", "English Sub"],
"default_languages": ["German Dub", "German Sub", "English Sub", "English Dub"],

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds English Dub to _DEFAULT_SITE_CONFIGS, but the AniWorld
CatalogProvider still declares only German Dub, German Sub, and
English Sub. Please either update both sources or explain why the provider
declaration intentionally differs. Two conflicting default-language lists
are likely to create path-dependent behavior later.

see apps/api/app/providers/aniworld/provider.py:32

)


def _label_from_lang_tuple(key: Any) -> Optional[str]:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper is described as decoding the aniworld v4 tuple contract, but it
also accepts raw strings, integers, multiple aliases, prefix matches, empty
subtitles, and "0". None of those extra formats are backed by a production
fixture or focused unit tests. Please keep the decoder aligned with the actual
upstream enum contract, or add evidence/tests for every tolerated format.
Broad guessing in compatibility code can turn upstream schema changes into
silently mislabeled releases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants