diff --git a/requirements.txt b/requirements.txt index 01e0530..0a9c345 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ hashids==1.2.0 enzyme>=0.4.1 click>=6.7 langdetect>=1.0.7 +titlecase>=0.12.0 diff --git a/src/cli.py b/src/cli.py index 074eb68..0abf09f 100755 --- a/src/cli.py +++ b/src/cli.py @@ -1,39 +1,25 @@ -#/usr/local/bin/python3 -import click -import os -import logging +#!usr/bin/env python3.6 -import env_variables as env +from core import scan_folder, moveHome +from video import Video +from guessit import guessit -logging.basicConfig(filename=env.logfile, level=logging.INFO) -logger = logging.getLogger('seasonedParser') -fh = logging.FileHandler(env.logfile) -fh.setLevel(logging.INFO) +from exceptions import InsufficientInfoError -formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') -fh.setFormatter(formatter) +videos, insufficient_info = scan_folder('Spider.Man') +print('Sweet lemonade: {} {}'.format(videos, insufficient_info)) -logger.addHandler(fh) +for video in videos: + moveHome(video) -def listPath(path): - if (os.path.isdir(path)): - print('Contents of path:') - print(os.listdir(path)) - - elif os.path.isfile(path): - print('File to parse:') - print(path) - - else: - print('Path does not exists') - -@click.command() -@click.argument('path') -@click.option('--greeting', '-g') -def main(path, greeting): - logger.info('Received cli variables: \n\t path: {}'.format(path)) - listPath(path) - - -if __name__ == '__main__': - main() +while len(insufficient_info) > 1: + for file in insufficient_info: + supplementary_info = input("Insufficient info for match file: '{}'\nSupplementary info: ".format(file)) + print(supplementary_info) + try: + video = Video.fromguess(file, guessit(supplementary_info)) + insufficient_info.pop() + except InsufficientInfoError: + pass + + moveHome(video) diff --git a/src/core.py b/src/core.py index d721319..79c83a4 100755 --- a/src/core.py +++ b/src/core.py @@ -19,13 +19,14 @@ from titlecase import titlecase import langdetect import env_variables as env +from exceptions import InsufficientInfoError from video import VIDEO_EXTENSIONS, Episode, Movie, Video from subtitle import SUBTITLE_EXTENSIONS, Subtitle, get_subtitle_path from utils import sanitize, refine logging.basicConfig(filename=env.logfile, level=logging.DEBUG) -logger = logging.getLogger('seasonedParser_core') +logger = logging.getLogger('seasonedParser') fh = logging.FileHandler(env.logfile) fh.setLevel(logging.DEBUG) sh = logging.StreamHandler() @@ -70,6 +71,9 @@ def search_external_subtitles(path, directory=None): return subtitles +def find_file_size(video): + return os.path.getsize(video.name) + def scan_video(path): """Scan a video from a `path`. @@ -92,8 +96,8 @@ def scan_video(path): # guess video = Video.fromguess(path, guessit(path)) - # size - video.size = os.path.getsize(path) + video.subtitles |= set(search_external_subtitles(video.name)) + refine(video) # hash of name # if isinstance(video, Movie): @@ -257,7 +261,7 @@ def save_subtitles(files, single=False, directory=None, encoding=None): def scan_folder(path): videos = [] - ignored_videos = [] + insufficient_info = [] errored_paths = [] logger.debug('Collecting path %s', path) @@ -271,40 +275,39 @@ def scan_folder(path): # if path is a file if os.path.isfile(path): logger.info('Path is a file') + try: video = scan_video(path) - except: - logger.exception('Unexpected error while collection file with path {}'.format(path)) - - video.subtitles |= set(search_external_subtitles(video.name)) + videos.append(video) - refine(video) - videos.append(video) + except InsufficientInfoError as e: + logger.error(e) + insufficient_info.append(path) # directories if os.path.isdir(path): logger.info('Path is a directory') + + scanned_videos = [] try: scanned_videos = scan_videos(path) + except InsufficientInfoError as e: + logger.error(e) + insufficient_info.append(path) except: logger.exception('Unexpected error while collecting directory path %s', path) errored_paths.append(path) - # Iterates over our scanned videos - with click.progressbar(scanned_videos, label='Parsing videos') as bar: - for v in bar: - v.subtitles |= set(search_external_subtitles(v.name)) - refine(v) - videos.append(v) - - click.echo('%s video%s collected / %s error%s' % ( + click.echo('%s video%s collected / %s file%s with insufficient info / %s error%s' % ( click.style(str(len(videos)), bold=True, fg='green' if videos else None), 's' if len(videos) > 1 else '', + click.style(str(len(insufficient_info)), bold=True, fg='yellow' if insufficient_info else None), + 's' if len(insufficient_info) > 1 else '', click.style(str(len(errored_paths)), bold=True, fg='red' if errored_paths else None), 's' if len(errored_paths) > 1 else '', )) - return videos + return videos, insufficient_info def pickforgirlscouts(video): if video.sufficientInfo(): @@ -314,47 +317,20 @@ def pickforgirlscouts(video): return False def moveHome(video): - dir = os.path.dirname(video.home) + wantedFilePath = video.wantedFilePath() + dir = os.path.dirname(wantedFilePath) if not os.path.exists(dir): logger.info('Creating directory {}'.format(dir)) os.makedirs(dir) - logger.info("Moving video file from: '{}' to: '{}'".format(video.name, video.home)) - shutil.move(video.name, video.home) + logger.info("Moving video file from: '{}' to: '{}'".format(video.name, wantedFilePath)) + shutil.move(video.name, wantedFilePath) for sub in video.subtitles: if not os.path.isfile(sub): continue oldpath = sub - newpath = subtitle_path(video.home, sub) + newpath = subtitle_path(wantedFilePath, sub) logger.info("Moving subtitle file from: '{}' to: '{}'".format(oldpath, newpath)) shutil.move(oldpath, newpath) -def main(): - path = '/mnt/mainframe/' - - videos = scan_folder(path) - - scout = [] - civilian = [] - for video in videos: - if pickforgirlscouts(video): - scout.append(video) - else: - civilian.append(video) - - click.echo('%s scout%s collected / %s civilan%s / %s candidate%s' % ( - click.style(str(len(scout)), bold=True, fg='green' if scout else None), - 's' if len(scout) > 1 else '', - click.style(str(len(civilian)), bold=True, fg='red' if civilian else None), - 's' if len(civilian) > 1 else '', - click.style(str(len(videos)), bold=True, fg='blue' if videos else None), - 's' if len(videos) > 1 else '' - )) - - for video in scout: - moveHome(video) - -if __name__ == '__main__': - main() - diff --git a/src/video.py b/src/video.py index baf2348..78af278 100644 --- a/src/video.py +++ b/src/video.py @@ -12,8 +12,9 @@ from titlecase import titlecase import hashlib, tvdb_api import env_variables as env +from exceptions import InsufficientInfoError -logger = logging.getLogger('seasonedParser_core') +logger = logging.getLogger('seasonedParser') #: Video extensions VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik', @@ -33,13 +34,13 @@ class Video(object): :param str resolution: resolution of the video stream (480p, 720p, 1080p or 1080i, 4K). :param str video_codec: codec of the video stream. :param str audio_codec: codec of the main audio stream. - :param str home: optimal parent folder. + :param str move_location: location to move file to. :param dict name_hash: hashes of the video file by provider names. :param int size: size of the video file in bytes. :param set subtitles: existing subtitle languages. """ def __init__(self, name, hash=None, size=None, format=None, release_group=None, resolution=None, video_codec=None, audio_codec=None, - home=None, subtitles=None, embeded_subtitles=None): + move_location=None, subtitles=None, embeded_subtitles=None): #: Name or path of the video self.name = name @@ -64,8 +65,8 @@ class Video(object): #: Codec of the main audio stream self.audio_codec = audio_codec - #: optimal home path; parent folder. - self.home = home + #: optimal move_location path; parent folder. + self.move_location = move_location #: Existing subtitle languages self.subtitles = subtitles or set() @@ -159,8 +160,8 @@ class Episode(Video): if guess['type'] != 'episode': raise ValueError('The guess must be an episode guess') - if 'title' not in guess or 'episode' not in guess: - raise ValueError('Insufficient data to process the guess') + if 'title' not in guess or 'season' not in guess or 'episode' not in guess: + raise InsufficientInfoError('Insufficient data to process the guess') return cls(name, guess['title'], guess.get('season', 1), guess['episode'], title=guess.get('episode_title'), year=guess.get('year'), format=guess.get('format'), original_series='year' not in guess, @@ -171,26 +172,11 @@ class Episode(Video): def fromname(cls, name): return cls.fromguess(name, guessit(name, {'type': 'episode'})) - def sufficientInfo(self): - ser = hasattr(self, 'series') - sea = hasattr(self, 'season') - ep = hasattr(self, 'episode') - - if False in [ser, sea, ep]: - logger.error('{}, {} or {} found to have none value, manual correction required'.format(self.series, self.season, self.episode)) - return False - - if list in [type(self.series), type(self.season), type(self.episode)]: - logger.error('{}, {} or {} found to have list values, manual correction required'.format(self.series, self.season, self.episode)) - return False - - return True - - def moveLocation(self): + def wantedFilePath(self): series = titlecase(self.series) grandParent = '{}/{} Season {:02d}'.format(series, series, self.season) parent = '{} S{:02d}E{:02d}'.format(series, self.season, self.episode) - self.home = os.path.join(env.SHOWBASE, grandParent, parent, os.path.basename(self.name)) + return os.path.join(env.SHOWBASE, grandParent, parent, os.path.basename(self.name)) def __repr__(self): if self.year is None: @@ -220,15 +206,15 @@ class Movie(Video): if guess['type'] != 'movie': raise ValueError('The guess must be a movie guess') - if 'title' not in guess: - raise ValueError('Insufficient data to process the guess') + if 'title' not in guess or 'year' not in guess: + raise InsufficientInfoError('Insufficient data to process the guess') return cls(name, guess['title'], format=guess.get('format'), release_group=guess.get('release_group'), resolution=guess.get('screen_size'), video_codec=guess.get('video_codec'), audio_codec=guess.get('audio_codec'), year=guess.get('year')) @classmethod - def fromname(cls, name): + def fromname(cls, name, year): return cls.fromguess(name, guessit(name, {'type': 'movie'})) def sufficientInfo(self): @@ -244,10 +230,10 @@ class Movie(Video): return True - def moveLocation(self): + def wantedFilePath(self): title = titlecase(self.title) parent = '{} ({})'.format(title, self.year) - self.home = os.path.join(env.MOVIEBASE, parent, os.path.basename(self.name)) + return os.path.join(env.MOVIEBASE, parent, os.path.basename(self.name)) def __repr__(self): if self.year is None: