feat: add category-based grouping for Sonarr series with anime and K-drama tag detection

Add tag fetching from Sonarr API and category_for() function to classify series based on normalized tags (anime, kdrama, standard). Parse tag labels/names into normalized strings removing non-alphanumeric characters for matching. Add category and category_label fields to series groups.

Update index route to build series_categories list grouping series by category key (anime/Anime, kdrama/K-Dramen, standard/Standard) and pass
This commit is contained in:
2026-08-15 12:37:58 +02:00
parent 2b8b0cd5e9
commit e49216dca5
2 changed files with 25 additions and 3 deletions
+23 -2
View File
@@ -372,7 +372,22 @@ def sonarr_groups():
groups = []
queue = api_get(SONARR_URL, SONARR_API_KEY, "queue?includeUnknownSeriesItems=true")
queued_episodes = {item.get("episodeId") for item in queue.get("records", [])}
tags = api_get(SONARR_URL, SONARR_API_KEY, "tag")
tag_names = {str(tag.get("id")): str(tag.get("label") or tag.get("name") or "").strip().casefold() for tag in tags}
def category_for(series):
normalized_tags = {
re.sub(r"[^a-z0-9]+", "", tag_names.get(str(tag_id), ""))
for tag_id in (series.get("tags") or [])
}
if "anime" in normalized_tags:
return "anime", "Anime"
if "kdrama" in normalized_tags:
return "kdrama", "K-Dramen"
return "standard", "Standard"
for series in api_get(SONARR_URL, SONARR_API_KEY, "series"):
category_key, category_label = category_for(series)
episodes = api_get(SONARR_URL, SONARR_API_KEY, f"episode?seriesId={series['id']}&includeSeries=false")
files = {f["id"]: f for f in api_get(SONARR_URL, SONARR_API_KEY, f"episodefile?seriesId={series['id']}")}
items = []
@@ -381,7 +396,8 @@ def sonarr_groups():
if ef: ef = dict(ef); ef["path"] = str(Path(series.get("path") or "") / ef.get("relativePath", ""))
label = f"S{episode.get('seasonNumber', 0):02d}E{episode.get('episodeNumber', 0):02d} · {episode.get('title') or ''}"
items.append(media_row(label, None, ef, SONARR_MEDIA_PATH, LOCAL_SERIES_PATH, {"episode":True, "season":episode.get("seasonNumber", 0), "downloading": episode.get("id") in queued_episodes}))
groups.append({"title":series.get("title", ""), "year":series.get("year"), "episodes":items})
groups.append({"title":series.get("title", ""), "year":series.get("year"), "episodes":items,
"category":category_key, "category_label":category_label})
return sorted(groups, key=lambda x: x["title"].lower())
def start_scan(source, force=False):
@@ -425,6 +441,10 @@ def index():
episodes = [episode for episode in group["episodes"] if is_german_review_candidate(episode) and not has_german_track(episode)]
if episodes:
missing_german_series.append({**group, "episodes": episodes})
series_categories = [
{"key": key, "label": label, "series": [group for group in series if group["category"] == key]}
for key, label in (("anime", "Anime"), ("kdrama", "K-Dramen"), ("standard", "Standard"))
]
dashboard_stats = {
"movie_total": len(rows),
"movie_files": sum(1 for row in rows if not row.get("missing")),
@@ -435,7 +455,8 @@ def index():
"episode_files": sum(1 for group in series for row in group["episodes"] if not row.get("missing")),
"episode_missing": sum(1 for group in series for row in group["episodes"] if row.get("missing") and not row.get("downloading")),
}
return render_template("index.html", rows=rows, series=series, missing_german_movies=missing_german_movies,
return render_template("index.html", rows=rows, series=series, series_categories=series_categories,
missing_german_movies=missing_german_movies,
missing_german_series=missing_german_series, error=error,
dashboard_stats=dashboard_stats,
sonarr_enabled=bool(SONARR_URL and SONARR_API_KEY), sab_enabled=bool(SAB_URL and SAB_API_KEY),