2021-06-13 11:49:21 +02:00
|
|
|
#!/usr/bin/env python3
|
2019-10-23 22:09:50 +02:00
|
|
|
# pylint: disable=C0103,W0621
|
|
|
|
|
|
|
|
# pip install tmdbv3api
|
|
|
|
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import typing
|
|
|
|
|
|
|
|
import tmdbv3api
|
|
|
|
|
|
|
|
# TODO Override files without warning
|
|
|
|
# TODO Dry run mode (just comment the last line ^^)
|
|
|
|
|
|
|
|
# Typing
|
|
|
|
|
|
|
|
Episode = typing.Any # TODO
|
|
|
|
|
|
|
|
# Constants
|
|
|
|
|
2021-06-13 11:49:21 +02:00
|
|
|
API_KEY_PASS_PATH = "http/themoviedb.org"
|
|
|
|
VIDEO_EXTENSIONS = {"mp4", "mkv", "avi", "webm"}
|
2019-10-23 22:09:50 +02:00
|
|
|
|
|
|
|
# Functions
|
|
|
|
|
|
|
|
|
|
|
|
def get_pass_data(path: str) -> typing.Dict[str, str]:
|
|
|
|
"""
|
|
|
|
Returns the data stored in the Unix password manager
|
|
|
|
given its path.
|
|
|
|
"""
|
2021-06-13 11:49:21 +02:00
|
|
|
run = subprocess.run(["pass", path], stdout=subprocess.PIPE, check=True)
|
|
|
|
lines = run.stdout.decode().split("\n")
|
2019-10-23 22:09:50 +02:00
|
|
|
data = dict()
|
2021-06-13 11:49:21 +02:00
|
|
|
data["pass"] = lines[0]
|
2019-10-23 22:09:50 +02:00
|
|
|
for line in lines[1:]:
|
2021-06-13 11:49:21 +02:00
|
|
|
match = re.match(r"(\w+): ?(.+)", line)
|
2019-10-23 22:09:50 +02:00
|
|
|
if match:
|
|
|
|
data[match[1]] = match[2]
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
def confirm(text: str) -> bool:
|
|
|
|
res = input(text + " [yn] ")
|
2021-06-13 11:49:21 +02:00
|
|
|
while res not in ("y", "n"):
|
2019-10-23 22:09:50 +02:00
|
|
|
res = input("Please answer with y or n: ")
|
2021-06-13 11:49:21 +02:00
|
|
|
return res == "y"
|
2019-10-23 22:09:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
def episode_identifier(episode: typing.Any) -> str:
|
2021-06-13 11:49:21 +02:00
|
|
|
return (
|
|
|
|
f"S{episode['season_number']:02d}E"
|
|
|
|
+ f"{episode['episode_number']:02d} {episode['name']}"
|
|
|
|
)
|
2019-10-23 22:09:50 +02:00
|
|
|
|
2021-06-13 11:49:21 +02:00
|
|
|
|
|
|
|
dryrun = "-n" in sys.argv
|
2019-10-23 22:09:50 +02:00
|
|
|
if dryrun:
|
|
|
|
dryrun = True
|
2021-06-13 11:49:21 +02:00
|
|
|
sys.argv.remove("-n")
|
2019-10-23 22:09:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
# Connecting to TMBDB
|
|
|
|
tmdb = tmdbv3api.TMDb()
|
2021-06-13 11:49:21 +02:00
|
|
|
tmdb.api_key = get_pass_data(API_KEY_PASS_PATH)["api"]
|
2019-10-23 22:09:50 +02:00
|
|
|
tmdb.language = sys.argv[1]
|
|
|
|
|
|
|
|
# Searching the TV show name (by current directory name)
|
|
|
|
tv = tmdbv3api.TV()
|
|
|
|
season = tmdbv3api.Season()
|
|
|
|
if len(sys.argv) >= 3:
|
|
|
|
show_name = sys.argv[2]
|
|
|
|
else:
|
|
|
|
show_name = os.path.split(os.path.realpath(os.path.curdir))[1]
|
2021-06-13 11:49:21 +02:00
|
|
|
if "(" in show_name:
|
|
|
|
show_name = show_name.split("(")[0].strip()
|
2019-10-23 22:09:50 +02:00
|
|
|
|
|
|
|
search = tv.search(show_name)
|
|
|
|
|
|
|
|
# Asking the user to select the one
|
|
|
|
show = None
|
|
|
|
|
|
|
|
for res in search:
|
|
|
|
print(f"#{res.id} {res.name} ({res.first_air_date[:4]}): {res.overview}")
|
|
|
|
if confirm("Is this the show for this folder?"):
|
|
|
|
show = tv.details(res.id)
|
|
|
|
break
|
|
|
|
|
|
|
|
if not show:
|
2021-06-13 11:49:21 +02:00
|
|
|
print("Could not find a matching " + f"show on TheMovieDatabase for {show_name}.")
|
2019-10-23 22:09:50 +02:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# Retrieving all the episode of the show
|
|
|
|
episodes: typing.List[Episode] = list()
|
|
|
|
|
|
|
|
print(f"List of episodes for {show.name}:")
|
2021-06-13 11:49:21 +02:00
|
|
|
for season_number in range(0, show.number_of_seasons + 1):
|
2019-10-23 22:09:50 +02:00
|
|
|
season_details = season.details(show.id, season_number)
|
|
|
|
|
|
|
|
try:
|
|
|
|
season_details.episodes
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
for episode in season_details.episodes:
|
|
|
|
episodes.append(episode)
|
|
|
|
print(f"- {episode_identifier(episode)}")
|
|
|
|
|
|
|
|
# Finding movie files in the folder
|
|
|
|
print("List of video files in this folder")
|
|
|
|
videos: typing.List[typing.Tuple[str, str]] = list()
|
|
|
|
for root, dirs, files in os.walk(os.path.curdir):
|
|
|
|
for filename in files:
|
|
|
|
basename, ext = os.path.splitext(filename)
|
|
|
|
real_ext = ext[1:].lower()
|
|
|
|
if real_ext not in VIDEO_EXTENSIONS:
|
|
|
|
continue
|
|
|
|
videos.append((root, filename))
|
|
|
|
print(f"- {filename}")
|
|
|
|
|
|
|
|
|
2021-06-13 11:49:21 +02:00
|
|
|
def get_episode(season_number: int, episode_number: int) -> typing.Optional[Episode]:
|
2019-10-23 22:09:50 +02:00
|
|
|
# TODO Make more efficient using indexing
|
|
|
|
for episode in episodes:
|
2021-06-13 11:49:21 +02:00
|
|
|
if (
|
|
|
|
episode["season_number"] == season_number
|
|
|
|
and episode["episode_number"] == episode_number
|
|
|
|
):
|
2019-10-23 22:09:50 +02:00
|
|
|
return episode
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# Matching movie files to episode
|
2021-06-13 11:49:21 +02:00
|
|
|
associations: typing.List[typing.Tuple[typing.Tuple[str, str], Episode]] = list()
|
2019-10-23 22:09:50 +02:00
|
|
|
for video in videos:
|
|
|
|
root, filename = video
|
2021-06-13 11:49:21 +02:00
|
|
|
match = re.search(r"S(\d+)E(\d+)", filename)
|
2019-10-23 22:09:50 +02:00
|
|
|
print(f"Treating file: {root}/{filename}")
|
|
|
|
episode = None
|
|
|
|
season_number = 0
|
|
|
|
episode_number = 0
|
|
|
|
while not episode:
|
|
|
|
if match:
|
|
|
|
season_number = int(match[1])
|
|
|
|
episode_number = int(match[2])
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
season_number = int(input("Season number ?"))
|
|
|
|
episode_number = int(input("Episode number ?"))
|
|
|
|
except ValueError:
|
|
|
|
continue
|
|
|
|
if season_number < 0 and episode_number < 0:
|
|
|
|
break
|
|
|
|
match = None
|
|
|
|
episode = get_episode(season_number, episode_number)
|
|
|
|
if not episode:
|
|
|
|
print(
|
2021-06-13 11:49:21 +02:00
|
|
|
f" could not find episode S{season_number:02d}E{episode_number:02d} in TMBD"
|
|
|
|
)
|
2019-10-23 22:09:50 +02:00
|
|
|
|
|
|
|
# Skip
|
|
|
|
if not episode:
|
|
|
|
if season_number < -1 and episode_number < -1:
|
|
|
|
# Skip all
|
|
|
|
break
|
|
|
|
# Skip one
|
|
|
|
continue
|
|
|
|
|
|
|
|
associations.append((video, episode))
|
|
|
|
print(f" associated to: {episode_identifier(episode)}")
|
|
|
|
|
|
|
|
|
|
|
|
# Rename video files
|
|
|
|
for association in associations:
|
|
|
|
video, episode = association
|
|
|
|
root, filename = video
|
|
|
|
basename, ext = os.path.splitext(filename)
|
|
|
|
new_name = f"{show.name} ({show.first_air_date[:4]}) {episode_identifier(episode)}"
|
|
|
|
# Rename all file with the same base name as the original file so we
|
|
|
|
# can rename nfo files and subtitles (only one though)
|
|
|
|
for a_filename in os.listdir(root):
|
|
|
|
a_basename, a_ext = os.path.splitext(a_filename)
|
|
|
|
if a_basename == basename:
|
|
|
|
old_path = os.path.join(root, a_filename)
|
|
|
|
new_path = os.path.join(root, new_name + a_ext)
|
2019-10-24 19:35:00 +02:00
|
|
|
if old_path == new_path:
|
|
|
|
continue
|
|
|
|
|
2019-10-23 22:09:50 +02:00
|
|
|
print(old_path, "->", new_path)
|
|
|
|
if not dryrun:
|
|
|
|
os.rename(old_path, new_path)
|