sonarr transslation script
functional name and id query get episodes from tmdb match episodes rename
This commit is contained in:
1 parent
a4da995ee1
commit
24b12ec6d6
1 file changed
+213
-104
+213
-104
@@ -1,8 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import requests
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
|
||||
TMDB_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJlOTIwMzJlMDk1NmEzMWEyZjQ4MDA4NGI0MTg4OTIyZCIsIm5iZiI6MTc5MDQzMjc5NS42NjcsInN1YiI6IjZhYjdkNjFiZDY5NmI1ZGFjNGM5NDRkMyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IjddKtQ5X62A63kYslH24Pxg_TFCzu-QpGak_Pemgr8"
|
||||
@@ -39,45 +42,56 @@ def tmdb_get(endpoint, params=None):
|
||||
|
||||
return response.json()
|
||||
|
||||
|
||||
def find_by_imdb(imdb_id):
|
||||
def find_by_title(title, media_type="tv", year=None, language="de-DE"):
|
||||
"""
|
||||
Find a movie/TV show/person using an IMDb ID.
|
||||
Search TMDB by title.
|
||||
|
||||
Example:
|
||||
tt1234567
|
||||
media_type:
|
||||
tv | movie
|
||||
"""
|
||||
|
||||
data = tmdb_get(
|
||||
f"/find/{imdb_id}",
|
||||
{
|
||||
"external_source": "imdb_id",
|
||||
},
|
||||
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,
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def find_by_tvdb(tvdb_id):
|
||||
def find_by_tmdb_id(tmdb_id, language="de-DE"):
|
||||
"""
|
||||
Try to find a TV show using a TVDB ID.
|
||||
Try to determine whether a TMDB ID is a TV show or movie.
|
||||
|
||||
TMDb's external ID system does not expose every external
|
||||
ID through the same /find mechanism, so we first try the
|
||||
external ID endpoint.
|
||||
Returns:
|
||||
("tv", data)
|
||||
("movie", data)
|
||||
(None, None)
|
||||
"""
|
||||
|
||||
data = tmdb_get(
|
||||
f"/find/{tvdb_id}",
|
||||
{
|
||||
"external_source": "tvdb_id",
|
||||
},
|
||||
)
|
||||
try:
|
||||
data = get_tv_details(tmdb_id, language)
|
||||
return "tv", data
|
||||
except requests.HTTPError:
|
||||
pass
|
||||
|
||||
return data
|
||||
try:
|
||||
data = get_movie_details(tmdb_id, language)
|
||||
return "movie", data
|
||||
except requests.HTTPError:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
def get_tv_details(tmdb_id):
|
||||
def get_tv_details(tmdb_id , language="de-DE"):
|
||||
"""
|
||||
Retrieve TV show details in German.
|
||||
"""
|
||||
@@ -85,134 +99,229 @@ def get_tv_details(tmdb_id):
|
||||
return tmdb_get(
|
||||
f"/tv/{tmdb_id}",
|
||||
{
|
||||
"language": "de-DE",
|
||||
"language": language,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_movie_details(tmdb_id):
|
||||
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)
|
||||
|
||||
def print_results(data):
|
||||
"""Print useful IDs returned by TMDb."""
|
||||
episodes = []
|
||||
|
||||
print()
|
||||
print("TMDb result")
|
||||
print("=" * 60)
|
||||
for season in show.get("seasons", []):
|
||||
season_number = season["season_number"]
|
||||
|
||||
movies = data.get("movie_results", [])
|
||||
tv = data.get("tv_results", [])
|
||||
# skip specials if desired
|
||||
if season_number == 0:
|
||||
continue
|
||||
|
||||
if movies:
|
||||
print()
|
||||
print("MOVIE")
|
||||
season_data = get_season(show.get("id"), season_number)
|
||||
|
||||
for movie in movies:
|
||||
print(f"TMDb ID: {movie.get('id')}")
|
||||
print(f"Title: {movie.get('title')}")
|
||||
print(f"Original: {movie.get('original_title')}")
|
||||
print(f"Release date: {movie.get('release_date')}")
|
||||
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"],
|
||||
})
|
||||
|
||||
if tv:
|
||||
print()
|
||||
print("TV SHOW")
|
||||
return episodes
|
||||
|
||||
for show in tv:
|
||||
print(f"TMDb ID: {show.get('id')}")
|
||||
print(f"Name: {show.get('name')}")
|
||||
print(f"Original: {show.get('original_name')}")
|
||||
print(f"First air: {show.get('first_air_date')}")
|
||||
|
||||
if not movies and not tv:
|
||||
print("No result found.")
|
||||
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():
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
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 imdb tt1234567")
|
||||
print(" python tmdb_lookup.py tvdb 417272")
|
||||
print(" python tmdb_lookup.py <Series Folder>")
|
||||
print(" Seires Folder needs to be this Style: Maya the Bee (1975) [10620]")
|
||||
sys.exit(1)
|
||||
|
||||
id_type = sys.argv[1].lower()
|
||||
external_id = sys.argv[2]
|
||||
series_path = Path(args.series)
|
||||
series = series_path.name
|
||||
m = re.match(r"^(.*?)\s*\((\d{4})\)\s*\[(\d+)\]$", series)
|
||||
|
||||
if id_type == "imdb":
|
||||
if not m:
|
||||
parser.error(f"Invalid series format: {args.series}")
|
||||
|
||||
print(f"Looking up IMDb ID: {external_id}")
|
||||
file_title = m.group(1).strip()
|
||||
file_year = int(m.group(2))
|
||||
file_tmdb_id = int(m.group(3))
|
||||
|
||||
data = find_by_imdb(external_id)
|
||||
print(f"TITLE={file_title}")
|
||||
print(f"YEAR={file_year}")
|
||||
print(f"TMDB_ID={file_tmdb_id}")
|
||||
|
||||
elif id_type == "tvdb":
|
||||
if file_tmdb_id == 0:
|
||||
print("Tmdb ID required")
|
||||
exit(1)
|
||||
|
||||
print(f"Looking up TVDB ID: {external_id}")
|
||||
print(f"Looking up IMDb ID: {file_tmdb_id}")
|
||||
|
||||
data = find_by_tvdb(external_id)
|
||||
|
||||
else:
|
||||
print("ERROR: ID type must be:")
|
||||
print(" imdb")
|
||||
print(" tvdb")
|
||||
sys.exit(1)
|
||||
data = find_by_tmdb_id(file_tmdb_id)
|
||||
media_type, media_data = data
|
||||
|
||||
print_results(data)
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Get detailed German metadata
|
||||
# --------------------------------------------------------
|
||||
episodes = get_episode_list(media_data)
|
||||
episode_map = {
|
||||
(ep["season"], ep["episode"]): ep
|
||||
for ep in episodes
|
||||
}
|
||||
|
||||
movies = data.get("movie_results", [])
|
||||
tv = data.get("tv_results", [])
|
||||
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
|
||||
|
||||
if tv:
|
||||
season = int(m.group(1))
|
||||
episode= int(m.group(2))
|
||||
|
||||
tmdb_id = tv[0]["id"]
|
||||
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,
|
||||
)
|
||||
|
||||
details = get_tv_details(tmdb_id)
|
||||
if m:
|
||||
prefix = m.group(1) # Andor (2022) - S01E07
|
||||
quality = m.group(3) or "" # [WEBRip-1080p][AAC 5.1][h265]
|
||||
|
||||
print()
|
||||
print("German TV metadata")
|
||||
print("=" * 60)
|
||||
new_name = (
|
||||
f"{prefix} - {ep['title']}"
|
||||
f"{(' ' + quality) if quality else ''}"
|
||||
f"{file.suffix}"
|
||||
)
|
||||
|
||||
print(f"TMDb ID: {details.get('id')}")
|
||||
print(f"German name: {details.get('name')}")
|
||||
print(f"Original name: {details.get('original_name')}")
|
||||
print(f"First air: {details.get('first_air_date')}")
|
||||
print(f"Overview: {details.get('overview')}")
|
||||
print(
|
||||
f"{file.name} -> "
|
||||
f"{new_name}"
|
||||
)
|
||||
|
||||
elif movies:
|
||||
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}")
|
||||
|
||||
tmdb_id = movies[0]["id"]
|
||||
answer = input("Rename files? [y/N]: ").strip().lower()
|
||||
if answer in ("y", "yes"):
|
||||
print("Proceeding...")
|
||||
|
||||
details = get_movie_details(tmdb_id)
|
||||
|
||||
print()
|
||||
print("German movie metadata")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"TMDb ID: {details.get('id')}")
|
||||
print(f"German title: {details.get('title')}")
|
||||
print(f"Original: {details.get('original_title')}")
|
||||
print(f"Release date: {details.get('release_date')}")
|
||||
print(f"Overview: {details.get('overview')}")
|
||||
|
||||
else:
|
||||
|
||||
print()
|
||||
print("No movie or TV result found.")
|
||||
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()
|
||||
Reference in new issue
Block a user