diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4837aa5..4837bee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## 0.51.2 - 2026-08-09
+
+- Changed Search to inspect both subbed and dubbed episode availability for each result.
+- Added `SUB` and `DUB` language badges to Search result cards when those episode versions are present.
+
## 0.51.1 - 2026-08-09
- Removed the manual add form from the Watchlist page so entries are added through Search.
diff --git a/README.md b/README.md
index 839edf3..55c073d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Kaizoku is a local web app for searching, tracking, and downloading anime from A
## Features
-- Search anime through the configured provider: `anikoto`, `anineko`, or `pahe`.
+- Search anime through the configured provider and tag results with available `SUB` and `DUB` episode languages.
- Queue single episodes or batches in subbed or dubbed mode.
- Fall back across the other configured providers when an episode or stream cannot be resolved on the primary provider.
- Save files with Jellyfin-friendly layout: `TV/Series Name/Season 01/Series Name - S01E01.mp4`.
diff --git a/VERSION b/VERSION
index 1e0c609..969eb25 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.51.1
+0.51.2
diff --git a/app.py b/app.py
index 9c0276a..05ab2f8 100644
--- a/app.py
+++ b/app.py
@@ -879,6 +879,43 @@ def cache_thumbnail_image(show_id, image_url):
return final_path.name
+def episode_language_summary(show_id, provider):
+ summary = {
+ "sub": False,
+ "dub": False,
+ "sub_count": 0,
+ "dub_count": 0,
+ "total": 0,
+ }
+ try:
+ rows = provider_bridge.episodes(show_id, provider=provider)
+ except Exception as exc:
+ debug_log("search.episode_language_summary_failed", show_id=show_id, provider=provider, error=exc)
+ return summary
+ sub_values = set()
+ dub_values = set()
+ all_values = set()
+ for row in rows:
+ value = str(row.get("number") or "").strip()
+ if value:
+ all_values.add(value)
+ langs = {str(lang or "").strip().lower() for lang in row.get("langs") or []}
+ if not langs:
+ langs.add("sub")
+ if "hsub" in langs:
+ langs.add("sub")
+ if "sub" in langs and value:
+ sub_values.add(value)
+ if "dub" in langs and value:
+ dub_values.add(value)
+ summary["sub"] = bool(sub_values)
+ summary["dub"] = bool(dub_values)
+ summary["sub_count"] = len(sub_values)
+ summary["dub_count"] = len(dub_values)
+ summary["total"] = len(all_values)
+ return summary
+
+
def search_anime(query, mode):
runtime = ensure_runtime()
provider = str((runtime.get("config") or {}).get("provider") or "anikoto").strip().lower()
@@ -890,13 +927,18 @@ def search_anime(query, mode):
show_id = str(show.get("id") or "").strip()
if not show_id or not title:
continue
+ language_summary = episode_language_summary(show_id, show.get("provider") or provider)
alternative_titles = extract_anidb_alternative_titles_for_title(title, primary_title=title)
index = len(results) + 1
results.append(
{
"id": show_id,
"title": title,
- "episodes": "",
+ "episodes": language_summary["total"],
+ "has_sub": language_summary["sub"],
+ "has_dub": language_summary["dub"],
+ "sub_episodes": language_summary["sub_count"],
+ "dub_episodes": language_summary["dub_count"],
"index": index,
"query": query,
"mode": mode,
diff --git a/search_page.py b/search_page.py
index 31b51c5..92a9d81 100644
--- a/search_page.py
+++ b/search_page.py
@@ -423,7 +423,7 @@ INDEX_HTML = r"""
-
+