fix(aniworld/torznab): restore English Sub/Dub releases (language decoder + specials-mapping guard)#132
Conversation
…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>
This comment was marked as resolved.
This comment was marked as resolved.
|
Thanks for your contribution; I am gonna take a look later... |
Zzackllack
left a comment
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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: | |||
There was a problem hiding this comment.
This is the riskiest behavior change in the PR, but neither new test exercises
it. Please add Torznab regression tests that prove:
- A real episode with
German Dubunavailable but another advertised
language is not remapped. - A genuine special still maps.
- A failed/unknown language-discovery request does not cause a normal episode
to be remapped. - 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): |
There was a problem hiding this comment.
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.
| "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"], |
There was a problem hiding this comment.
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]: |
There was a problem hiding this comment.
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.
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_languagesmapped each(Audio, Subtitles)key only through the upstreamINVERSE_LANG_KEY_MAP/LANG_LABELS, andcontinued onKeyError— silently dropping any English key the maps didn't recognise, so only German reached Sonarr. Thes.tobranch 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 onKeyError; s.to reuses it;_normalize_language_for_backendgets 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_englishtest_sto_available_languages_includes_subtitle_variantscd apps/api && uv run pytest tests/unit/core/downloader/test_episode.pyLive verification (dev stack)
S4E10feed now emitsGER.SUBandENG.SUBfor the real episode (magnetaw_s=4&aw_e=10); log showsSkipping special mapping ... treating as a normal episode.ArifuretaS2E11 (has English on AniWorld) emitsENG.SUB; S2E12 is correctly German-only (verified against the raw AniWorld page HTML — episode 12 has no English key yet).Out of scope (deferred)
s.to, which may lack Sub variants for a title.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests