Files
Server-scripts/sonar_translation.py
T
Lucas Vivo 24b12ec6d6 sonarr transslation script
functional name and id query
get episodes from tmdb
match episodes
rename
2026-10-03 12:56:09 +02:00

327 lines
8.3 KiB
Python

#!/usr/bin/env python3
import re
import sys
import requests
import argparse
from pathlib import Path
from difflib import SequenceMatcher
TMDB_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJlOTIwMzJlMDk1NmEzMWEyZjQ4MDA4NGI0MTg4OTIyZCIsIm5iZiI6MTc5MDQzMjc5NS42NjcsInN1YiI6IjZhYjdkNjFiZDY5NmI1ZGFjNGM5NDRkMyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IjddKtQ5X62A63kYslH24Pxg_TFCzu-QpGak_Pemgr8"
if not TMDB_TOKEN:
print("ERROR: TMDB_API_TOKEN is not set.")
print()
print("Example:")
print(" export TMDB_API_TOKEN='your_token'")
sys.exit(1)
BASE_URL = "https://api.themoviedb.org/3"
HEADERS = {
"Authorization": f"Bearer {TMDB_TOKEN}",
"accept": "application/json",
}
def tmdb_get(endpoint, params=None):
"""Perform a GET request against TMDb."""
url = BASE_URL + endpoint
response = requests.get(
url,
headers=HEADERS,
params=params,
timeout=15,
)
response.raise_for_status()
return response.json()
def find_by_title(title, media_type="tv", year=None, language="de-DE"):
"""
Search TMDB by title.
media_type:
tv | movie
"""
params = {
"query": title,
"language": language,
}
if year:
if media_type == "tv":
params["first_air_date_year"] = year
else:
params["year"] = year
return tmdb_get(
f"/search/{media_type}",
params,
)
def find_by_tmdb_id(tmdb_id, language="de-DE"):
"""
Try to determine whether a TMDB ID is a TV show or movie.
Returns:
("tv", data)
("movie", data)
(None, None)
"""
try:
data = get_tv_details(tmdb_id, language)
return "tv", data
except requests.HTTPError:
pass
try:
data = get_movie_details(tmdb_id, language)
return "movie", data
except requests.HTTPError:
pass
return None, None
def get_tv_details(tmdb_id , language="de-DE"):
"""
Retrieve TV show details in German.
"""
return tmdb_get(
f"/tv/{tmdb_id}",
{
"language": language,
},
)
def get_movie_details(tmdb_id, language="de-DE"):
"""
Retrieve movie details in German.
"""
return tmdb_get(
f"/movie/{tmdb_id}",
{
"language": language,
},
)
def get_season(tmdb_id, season_number):
return tmdb_get(
f"/tv/{tmdb_id}/season/{season_number}",
{
"language": "de-DE",
},
)
def get_episode_list(show):
#_, show = find_by_tmdb_id(tmdb_id)
episodes = []
for season in show.get("seasons", []):
season_number = season["season_number"]
# skip specials if desired
if season_number == 0:
continue
season_data = get_season(show.get("id"), season_number)
for episode in season_data.get("episodes", []):
episodes.append({
"season": season_number,
"episode": episode["episode_number"],
"title": episode["name"],
"air_date": episode["air_date"],
"runtime": episode.get("runtime"),
"tmdb_id": episode["id"],
})
return episodes
def print_results(result):
media_type, data = result
print(f"Type : {media_type}")
print(f"TMDB ID : {data.get('id')}")
print(f"Title : {data.get('name') or data.get('title')}")
print(f"Original : {data.get('original_name') or data.get('original_title')}")
print(f"Year : {(data.get('first_air_date') or data.get('release_date') or '')[:4]}")
print(f"Rating : {data.get('vote_average')}")
print(f"Votes : {data.get('vote_count')}")
print(f"Status : {data.get('status')}")
print(f"Tagline : {data.get('tagline')}")
print(f"Overview : {data.get('overview')}")
genres = ", ".join(g["name"] for g in data.get("genres", []))
print(f"Genres : {genres}")
print(f"Poster : https://image.tmdb.org/t/p/original{data.get('poster_path', '')}")
print(f"Backdrop : https://image.tmdb.org/t/p/original{data.get('backdrop_path', '')}")
if media_type == "tv":
print(f"Seasons : {data.get('number_of_seasons')}")
print(f"Episodes : {data.get('number_of_episodes')}")
def normalize(text, title, year):
text = text.lower()
# remove series title
text = re.sub(re.escape(title.lower()), " ", text)
# remove year
text = re.sub(rf"\b{year}\b", " ", text)
# remove anything in [] including the brackets
text = re.sub(r"\[[^\]]*\]", " ", text)
# remove punctuation
text = re.sub(r"[^\w\s]", " ", text)
# remove episode numbers like S01E01
text = re.sub(r"\bs\d+e\d+\b", " ", text, flags=re.I)
# collapse spaces
text = re.sub(r"\s+", " ", text).strip()
return text
def best_episode_match(filename, episodes,title,year):
filename_norm = normalize(Path(filename).stem, title, year)
best = None
best_score = 0
for ep in episodes:
score = SequenceMatcher(
None,
filename_norm,
normalize(ep["title"], title, year)
).ratio()
if score > best_score:
best_score = score
best = ep
return best, best_score
def main():
parser = argparse.ArgumentParser()
parser.add_argument("series", help='Series name, e.g. "Maya the Bee (1975) [10620]"')
args = parser.parse_args()
if len(sys.argv) != 2 or not Path(args.series).is_dir():
print("Usage:")
print()
print(" python tmdb_lookup.py <Series Folder>")
print(" Seires Folder needs to be this Style: Maya the Bee (1975) [10620]")
sys.exit(1)
series_path = Path(args.series)
series = series_path.name
m = re.match(r"^(.*?)\s*\((\d{4})\)\s*\[(\d+)\]$", series)
if not m:
parser.error(f"Invalid series format: {args.series}")
file_title = m.group(1).strip()
file_year = int(m.group(2))
file_tmdb_id = int(m.group(3))
print(f"TITLE={file_title}")
print(f"YEAR={file_year}")
print(f"TMDB_ID={file_tmdb_id}")
if file_tmdb_id == 0:
print("Tmdb ID required")
exit(1)
print(f"Looking up IMDb ID: {file_tmdb_id}")
data = find_by_tmdb_id(file_tmdb_id)
media_type, media_data = data
print_results(data)
episodes = get_episode_list(media_data)
episode_map = {
(ep["season"], ep["episode"]): ep
for ep in episodes
}
for file in series_path.rglob("*"):
m = re.search(r"S(\d{2})E(\d{2})", file.name, re.I)
if not m:
m_b = re.search(r"Season (\d{2})", file.name, re.I)
if m_b:
continue
print(f"No season/episode number found: {file.name}")
continue
season = int(m.group(1))
episode= int(m.group(2))
ep = episode_map.get((season, episode))
file_list = []
new_file_list = []
if ep:
m = re.match(
r"^(.*?S\d{2}E\d{2})\s*-\s*(.*?)\s*(\[[^\]]+\].*)?$",
file.stem,
re.I,
)
if m:
prefix = m.group(1) # Andor (2022) - S01E07
quality = m.group(3) or "" # [WEBRip-1080p][AAC 5.1][h265]
new_name = (
f"{prefix} - {ep['title']}"
f"{(' ' + quality) if quality else ''}"
f"{file.suffix}"
)
print(
f"{file.name} -> "
f"{new_name}"
)
file_list.append(file)
new_file_list.append(new_name)
else:
print(
f"Episod* not found: "
f"S{season:02d}E{episode:02d}"
)
title=(f"{(data.get('name') or data.get('title'))} "
f"({(data.get('first_air_date') or data.get('release_date') or '')[:4]})")
print(f"{series} -> {title}")
answer = input("Rename files? [y/N]: ").strip().lower()
if answer in ("y", "yes"):
print("Proceeding...")
file_list.append(series_path)
new_file_list.append(title)
for file, new_file in zip(file_list,new_file_list):
file.rename(file.with_name(new_file))
else:
print("Cancelled.")
if __name__ == "__main__":
main()