diff --git a/config/scripts/tvshow b/config/scripts/tvshow new file mode 100755 index 0000000..7052c5d --- /dev/null +++ b/config/scripts/tvshow @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# 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 + +API_KEY_PASS_PATH = 'http/themoviedb.org' +VIDEO_EXTENSIONS = {'mp4', 'mkv', 'avi', 'webm'} + +# Functions + + +def get_pass_data(path: str) -> typing.Dict[str, str]: + """ + Returns the data stored in the Unix password manager + given its path. + """ + run = subprocess.run(['pass', path], stdout=subprocess.PIPE, check=True) + lines = run.stdout.decode().split('\n') + data = dict() + data['pass'] = lines[0] + for line in lines[1:]: + match = re.match(r'(\w+): ?(.+)', line) + if match: + data[match[1]] = match[2] + return data + + +def confirm(text: str) -> bool: + res = input(text + " [yn] ") + while res not in ('y', 'n'): + res = input("Please answer with y or n: ") + return res == 'y' + + +def episode_identifier(episode: typing.Any) -> str: + return f"S{episode['season_number']:02d}E" + \ + f"{episode['episode_number']:02d} {episode['name']}" + +dryrun = '-n' in sys.argv +if dryrun: + dryrun = True + sys.argv.remove('-n') + + +# Connecting to TMBDB +tmdb = tmdbv3api.TMDb() +tmdb.api_key = get_pass_data(API_KEY_PASS_PATH)['api'] +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] + +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: + print("Could not find a matching " + + f"show on TheMovieDatabase for {show_name}.") + sys.exit(1) + +# Retrieving all the episode of the show +episodes: typing.List[Episode] = list() + +print(f"List of episodes for {show.name}:") +for season_number in range(0, show.number_of_seasons+1): + 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}") + + +def get_episode(season_number: int, episode_number: int + ) -> typing.Optional[Episode]: + # TODO Make more efficient using indexing + for episode in episodes: + if episode['season_number'] == season_number \ + and episode['episode_number'] == episode_number: + return episode + return None + + +# Matching movie files to episode +associations: typing.List[typing.Tuple[typing.Tuple[str, + str], Episode]] = list() +for video in videos: + root, filename = video + match = re.search(r'S(\d+)E(\d+)', filename) + 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( + f" could not find episode S{season_number:02d}E{episode_number:02d} in TMBD") + + # 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) + print(old_path, "->", new_path) + if not dryrun: + os.rename(old_path, new_path)