| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- # Imports
- import re
- import os
- import pathlib
- import argparse
- from datetime import datetime
- import tvdb_v4_official
- # Default config
- default_apikey = os.path.expanduser('~/.tvdb_apikey')
- default_lang = 'eng'
- default_order = 'default'
- # Local helpers
- def readConfigLine(file, opt=False):
- value = None
- try:
- with open(file, 'r') as f:
- for line in f:
- if re.match('^\\s*#', line):
- continue
- else:
- value = line.strip()
- break
- except Exception as error:
- if not opt or cli.verbose:
- print('Unable to read config: {e}'.format(e=error))
- return value
- # CLI config
- parser = argparse.ArgumentParser('TVDB Renamer')
- parser.add_argument('--verbose', '-v', action='store_true', help='Display debug and episode data')
- parser.add_argument('--api', default=default_apikey, help='Path to a file containing the TVDB API key')
- parser.add_argument('--lang', '-l', help='Force TVDB language code')
- parser.add_argument('--order', '-o', help='Force TVDB order type')
- parser.add_argument('path', type=pathlib.Path, help='The directory containing files to be renamed')
- cli = parser.parse_args()
- if not os.path.isfile(cli.api):
- print('Invalid API key file path: {path}'.format(path=cli.api))
- exit(1)
- if not os.path.isdir(cli.path):
- print('Invalid path: {path}'.format(path=cli.path))
- exit(1)
- if cli.lang == 'None' or cli.lang == '':
- cli.lang = None
- if cli.verbose:
- print('Path: {p}'.format(p=cli.path))
- print('Forced Lang/Order: {l}/{o}'.format(l=cli.lang, o=cli.order))
- # Move to keep the file paths flat
- try:
- os.chdir(cli.path)
- except Exception as error:
- print('Unable to move to target directory: {e}'.format(e=error))
- exit(1)
- # Read the local directory
- seasons = {}
- try:
- dirfiles = os.listdir('.')
- except Exception as error:
- print('Unable to read to target directory: {e}'.format(e=error))
- exit(1)
- for file in dirfiles:
- f = {}
- f['file'] = file
- try:
- split = re.search(r'^(.+?)(\.(?:\w\w\.)?(?:sdh\.)?(?:synced\.)?\w{3,4}(?:-remux)?)$', file, re.IGNORECASE)
- if split is not None:
- f['base'] = split.group(1)
- f['extension'] = split.group(2)
- else:
- f['base'] = file
- f['extension'] = ''
- se = re.search(r'S(\d+)E(\d+)', file, flags=re.IGNORECASE)
- if se is None:
- # Does not contain a season/episode block
- continue
- f['season'] = int(se.group(1))
- f['number'] = int(se.group(2))
- if not f['season'] in seasons:
- seasons[ f['season'] ] = {}
- if not f['number'] in seasons[ f['season'] ]:
- seasons[ f['season'] ][ f['number'] ] = []
- seasons[ f['season'] ][ f['number'] ].append(f)
- except Exception as error:
- print('Unable to parse filename "{f}": {e}'.format(e=error, f=file))
- continue
- # Fetch the TVDB episode list
- try:
- apikey = readConfigLine(cli.api)
- id = readConfigLine('.tvdb', opt=True)
- if cli.lang is not None:
- lang = cli.lang
- else:
- lang = readConfigLine('.tvdb_lang', opt=True)
- if lang is None:
- lang = default_lang
- if cli.order is not None:
- order = cli.order
- else:
- order = readConfigLine('.tvdb_order', opt=True)
- if order is None:
- order = default_order
- if apikey is None or id is None:
- if cli.verbose:
- print('Invalid TVDB parameters')
- exit(1)
- if cli.verbose:
- print('Series: {id}'.format(id=id))
- print('Lang/Order: {l}/{o}'.format(l=lang, o=order))
- tvdb = tvdb_v4_official.TVDB(apikey)
- if lang is None:
- series = tvdb.get_series(id=id)
- else:
- series = tvdb.get_series_translation(id=id, lang=lang)
- data = tvdb.get_series_episodes(id=id, lang=lang, season_type=order)
- del tvdb
- del id
- del apikey
- except Exception as error:
- print(
- "Unable to fetch series {id}: {e}\nSeries:\n{s}\nEpisodes:\n{ep}".format(
- id=id, e=error, s=series, ep=data
- )
- )
- exit(1)
- # Parse and clean the data we need
- episodes = []
- try:
- series_name = str('_NoSeriesName' if series['name'] is None else series['name']).strip()
- if cli.verbose:
- print('Series: {name}'.format(name=series_name))
- for episode in data['episodes']:
- e = {}
- e['series'] = series_name
- e['season'] = int(episode['seasonNumber'])
- e['number'] = int(episode['number'])
- e['name'] = str('(Untitled)' if episode['name'] is None else episode['name']).strip()
- e['date'] = None if episode['aired'] is None else datetime.strptime(episode['aired'], '%Y-%m-%d')
- e['description'] = '' if episode['overview'] is None else episode['overview'].strip()
- e['filename'] = '{series} - S{season:02d}E{number:02d} - {name}'.format(
- series=e['series'], season=e['season'], number=e['number'], name=e['name']
- )
- e['filename'] = e['filename'].replace(':', ' -').replace('/', ' - ').replace(' ', ' ')
- episodes.append(e)
- except Exception as error:
- print('Unable to parse episode data: {e}'.format(e=error))
- exit(1)
- # Find unexpected episodes
- if cli.verbose:
- db_seasons = {}
- for e in episodes:
- if not e['season'] in db_seasons:
- db_seasons[ e['season'] ] = {}
- if not e['number'] in db_seasons[ e['season'] ]:
- db_seasons[ e['season'] ][ e['number'] ] = True
- for snum, s in seasons.items():
- for enum, elist in s.items():
- for e in elist:
- if (
- not e['season'] in db_seasons or
- not e['number'] in db_seasons[ e['season'] ]
- ):
- print('Unexpected: {f}'.format(f=e['file']))
- del db_seasons
- # Rename matched files
- for e in episodes:
- if (
- not e['season'] in seasons or
- not e['number'] in seasons[ e['season'] ]
- ):
- if cli.verbose:
- if e['date'] is None or e['date'] <= datetime.today():
- print('Missing: {m}'.format(m=e['filename']))
- continue
- files = seasons[ e['season'] ][ e['number'] ]
- for file in files:
- if file['base'] != e['filename']:
- src = file['file']
- dest = e['filename'] + file['extension']
- print('Rename\n\t{s}\n\t{d}'.format(s=src, d=dest))
- if (
- os.path.exists(dest) and
- file['base'].casefold() != e['filename'].casefold()
- ):
- print('Target exists: {d}'.format(d=dest))
- continue
- try:
- os.rename(src, dest)
- except Exception as error:
- print('Unable to rename {s}: {e}'.format(s=src, e=error))
- continue
- else:
- if cli.verbose:
- print('Found: {m}'.format(m=e['filename']))
|