From 17d82b84c20e83379c84585c26847700114444d8 Mon Sep 17 00:00:00 2001 From: Breadway Date: Tue, 21 Jul 2026 20:09:24 +0800 Subject: [PATCH] Fetch TVDB's English-translated episode names instead of original-language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plain /episodes/default returns names in the show's original airing language — for a lot of anime that's Japanese with no English name at all, which is what was ending up embedded in generated filenames. TVDB carries real crowd-sourced English translations at /episodes/default/eng (same numbering/air-date fields, just a better name) — try that first and only fall back to the original-language endpoint if a show has no English data. --- breadarrd/src/metadata/tvdb.rs | 57 ++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/breadarrd/src/metadata/tvdb.rs b/breadarrd/src/metadata/tvdb.rs index 914022e..a9cd5ff 100644 --- a/breadarrd/src/metadata/tvdb.rs +++ b/breadarrd/src/metadata/tvdb.rs @@ -110,8 +110,6 @@ impl TvdbClient { } pub async fn episodes(&self, series_id: &str) -> Result> { - let token = self.token().await?; - #[derive(Deserialize)] struct EpisodesResponse { data: EpisodesData, @@ -131,20 +129,47 @@ impl TvdbClient { aired: Option, } - let resp: EpisodesResponse = self - .client - .get(format!( - "https://api4.thetvdb.com/v4/series/{series_id}/episodes/default" - )) - .bearer_auth(token) - .send() - .await - .context("tvdb episodes request failed")? - .error_for_status() - .context("tvdb episodes returned an error status")? - .json() - .await - .context("tvdb episodes response was not valid JSON")?; + // TVDB's plain `/episodes/default` returns names in the show's + // original airing language — for a lot of anime that's Japanese, + // with no English name at all (verified live: entire shows came + // back Japanese-only). `/episodes/default/eng` is the same episode + // list (same numbering/air-date fields) but with TVDB's own + // crowd-sourced English translation substituted in for `name` + // wherever one exists — a real translation, not a guess, so it's + // tried first and only falls back to the original-language + // endpoint if TVDB has no English data for this show at all. + let token = self.token().await?; + let fetch = |url: String| { + let client = self.client.clone(); + let token = token.clone(); + async move { + client + .get(url) + .bearer_auth(token) + .send() + .await + .context("tvdb episodes request failed")? + .error_for_status() + .context("tvdb episodes returned an error status")? + .json::() + .await + .context("tvdb episodes response was not valid JSON") + } + }; + + let resp = match fetch(format!( + "https://api4.thetvdb.com/v4/series/{series_id}/episodes/default/eng" + )) + .await + { + Ok(resp) => resp, + Err(_) => { + fetch(format!( + "https://api4.thetvdb.com/v4/series/{series_id}/episodes/default" + )) + .await? + } + }; Ok(resp .data