Merge pull request #3 from KevinMidboe/CLI

Cli implimintation
This commit is contained in:
2018-10-17 23:29:53 +02:00
committed by GitHub
4 changed files with 63 additions and 114 deletions

View File

@@ -4,3 +4,4 @@ hashids==1.2.0
enzyme>=0.4.1 enzyme>=0.4.1
click>=6.7 click>=6.7
langdetect>=1.0.7 langdetect>=1.0.7
titlecase>=0.12.0

View File

@@ -1,39 +1,25 @@
#/usr/local/bin/python3 #!usr/bin/env python3.6
import click
import os
import logging
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) from exceptions import InsufficientInfoError
logger = logging.getLogger('seasonedParser')
fh = logging.FileHandler(env.logfile)
fh.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') videos, insufficient_info = scan_folder('Spider.Man')
fh.setFormatter(formatter) print('Sweet lemonade: {} {}'.format(videos, insufficient_info))
logger.addHandler(fh) for video in videos:
moveHome(video)
def listPath(path): while len(insufficient_info) > 1:
if (os.path.isdir(path)): for file in insufficient_info:
print('Contents of path:') supplementary_info = input("Insufficient info for match file: '{}'\nSupplementary info: ".format(file))
print(os.listdir(path)) print(supplementary_info)
try:
elif os.path.isfile(path): video = Video.fromguess(file, guessit(supplementary_info))
print('File to parse:') insufficient_info.pop()
print(path) except InsufficientInfoError:
pass
else:
print('Path does not exists') moveHome(video)
@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()

View File

@@ -19,13 +19,14 @@ from titlecase import titlecase
import langdetect import langdetect
import env_variables as env import env_variables as env
from exceptions import InsufficientInfoError
from video import VIDEO_EXTENSIONS, Episode, Movie, Video from video import VIDEO_EXTENSIONS, Episode, Movie, Video
from subtitle import SUBTITLE_EXTENSIONS, Subtitle, get_subtitle_path from subtitle import SUBTITLE_EXTENSIONS, Subtitle, get_subtitle_path
from utils import sanitize, refine from utils import sanitize, refine
logging.basicConfig(filename=env.logfile, level=logging.DEBUG) logging.basicConfig(filename=env.logfile, level=logging.DEBUG)
logger = logging.getLogger('seasonedParser_core') logger = logging.getLogger('seasonedParser')
fh = logging.FileHandler(env.logfile) fh = logging.FileHandler(env.logfile)
fh.setLevel(logging.DEBUG) fh.setLevel(logging.DEBUG)
sh = logging.StreamHandler() sh = logging.StreamHandler()
@@ -70,6 +71,9 @@ def search_external_subtitles(path, directory=None):
return subtitles return subtitles
def find_file_size(video):
return os.path.getsize(video.name)
def scan_video(path): def scan_video(path):
"""Scan a video from a `path`. """Scan a video from a `path`.
@@ -92,8 +96,8 @@ def scan_video(path):
# guess # guess
video = Video.fromguess(path, guessit(path)) video = Video.fromguess(path, guessit(path))
# size video.subtitles |= set(search_external_subtitles(video.name))
video.size = os.path.getsize(path) refine(video)
# hash of name # hash of name
# if isinstance(video, Movie): # if isinstance(video, Movie):
@@ -257,7 +261,7 @@ def save_subtitles(files, single=False, directory=None, encoding=None):
def scan_folder(path): def scan_folder(path):
videos = [] videos = []
ignored_videos = [] insufficient_info = []
errored_paths = [] errored_paths = []
logger.debug('Collecting path %s', path) logger.debug('Collecting path %s', path)
@@ -271,40 +275,39 @@ def scan_folder(path):
# if path is a file # if path is a file
if os.path.isfile(path): if os.path.isfile(path):
logger.info('Path is a file') logger.info('Path is a file')
try: try:
video = scan_video(path) video = scan_video(path)
except: videos.append(video)
logger.exception('Unexpected error while collection file with path {}'.format(path))
video.subtitles |= set(search_external_subtitles(video.name))
refine(video) except InsufficientInfoError as e:
videos.append(video) logger.error(e)
insufficient_info.append(path)
# directories # directories
if os.path.isdir(path): if os.path.isdir(path):
logger.info('Path is a directory') logger.info('Path is a directory')
scanned_videos = []
try: try:
scanned_videos = scan_videos(path) scanned_videos = scan_videos(path)
except InsufficientInfoError as e:
logger.error(e)
insufficient_info.append(path)
except: except:
logger.exception('Unexpected error while collecting directory path %s', path) logger.exception('Unexpected error while collecting directory path %s', path)
errored_paths.append(path) errored_paths.append(path)
# Iterates over our scanned videos click.echo('%s video%s collected / %s file%s with insufficient info / %s error%s' % (
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.style(str(len(videos)), bold=True, fg='green' if videos else None), click.style(str(len(videos)), bold=True, fg='green' if videos else None),
's' if len(videos) > 1 else '', '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), click.style(str(len(errored_paths)), bold=True, fg='red' if errored_paths else None),
's' if len(errored_paths) > 1 else '', 's' if len(errored_paths) > 1 else '',
)) ))
return videos return videos, insufficient_info
def pickforgirlscouts(video): def pickforgirlscouts(video):
if video.sufficientInfo(): if video.sufficientInfo():
@@ -314,47 +317,20 @@ def pickforgirlscouts(video):
return False return False
def moveHome(video): def moveHome(video):
dir = os.path.dirname(video.home) wantedFilePath = video.wantedFilePath()
dir = os.path.dirname(wantedFilePath)
if not os.path.exists(dir): if not os.path.exists(dir):
logger.info('Creating directory {}'.format(dir)) logger.info('Creating directory {}'.format(dir))
os.makedirs(dir) os.makedirs(dir)
logger.info("Moving video file from: '{}' to: '{}'".format(video.name, video.home)) logger.info("Moving video file from: '{}' to: '{}'".format(video.name, wantedFilePath))
shutil.move(video.name, video.home) shutil.move(video.name, wantedFilePath)
for sub in video.subtitles: for sub in video.subtitles:
if not os.path.isfile(sub): if not os.path.isfile(sub):
continue continue
oldpath = sub oldpath = sub
newpath = subtitle_path(video.home, sub) newpath = subtitle_path(wantedFilePath, sub)
logger.info("Moving subtitle file from: '{}' to: '{}'".format(oldpath, newpath)) logger.info("Moving subtitle file from: '{}' to: '{}'".format(oldpath, newpath))
shutil.move(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()

View File

@@ -12,8 +12,9 @@ from titlecase import titlecase
import hashlib, tvdb_api import hashlib, tvdb_api
import env_variables as env import env_variables as env
from exceptions import InsufficientInfoError
logger = logging.getLogger('seasonedParser_core') logger = logging.getLogger('seasonedParser')
#: Video extensions #: Video extensions
VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik', 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 resolution: resolution of the video stream (480p, 720p, 1080p or 1080i, 4K).
:param str video_codec: codec of the video stream. :param str video_codec: codec of the video stream.
:param str audio_codec: codec of the main audio 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 dict name_hash: hashes of the video file by provider names.
:param int size: size of the video file in bytes. :param int size: size of the video file in bytes.
:param set subtitles: existing subtitle languages. :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, 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 #: Name or path of the video
self.name = name self.name = name
@@ -64,8 +65,8 @@ class Video(object):
#: Codec of the main audio stream #: Codec of the main audio stream
self.audio_codec = audio_codec self.audio_codec = audio_codec
#: optimal home path; parent folder. #: optimal move_location path; parent folder.
self.home = home self.move_location = move_location
#: Existing subtitle languages #: Existing subtitle languages
self.subtitles = subtitles or set() self.subtitles = subtitles or set()
@@ -159,8 +160,8 @@ class Episode(Video):
if guess['type'] != 'episode': if guess['type'] != 'episode':
raise ValueError('The guess must be an episode guess') raise ValueError('The guess must be an episode guess')
if 'title' not in guess or 'episode' not in guess: if 'title' not in guess or 'season' not in guess or 'episode' not in guess:
raise ValueError('Insufficient data to process the 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'), 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, 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): def fromname(cls, name):
return cls.fromguess(name, guessit(name, {'type': 'episode'})) return cls.fromguess(name, guessit(name, {'type': 'episode'}))
def sufficientInfo(self): def wantedFilePath(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):
series = titlecase(self.series) series = titlecase(self.series)
grandParent = '{}/{} Season {:02d}'.format(series, series, self.season) grandParent = '{}/{} Season {:02d}'.format(series, series, self.season)
parent = '{} S{:02d}E{:02d}'.format(series, self.season, self.episode) 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): def __repr__(self):
if self.year is None: if self.year is None:
@@ -220,15 +206,15 @@ class Movie(Video):
if guess['type'] != 'movie': if guess['type'] != 'movie':
raise ValueError('The guess must be a movie guess') raise ValueError('The guess must be a movie guess')
if 'title' not in guess: if 'title' not in guess or 'year' not in guess:
raise ValueError('Insufficient data to process the guess') raise InsufficientInfoError('Insufficient data to process the guess')
return cls(name, guess['title'], format=guess.get('format'), release_group=guess.get('release_group'), 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'), resolution=guess.get('screen_size'), video_codec=guess.get('video_codec'),
audio_codec=guess.get('audio_codec'), year=guess.get('year')) audio_codec=guess.get('audio_codec'), year=guess.get('year'))
@classmethod @classmethod
def fromname(cls, name): def fromname(cls, name, year):
return cls.fromguess(name, guessit(name, {'type': 'movie'})) return cls.fromguess(name, guessit(name, {'type': 'movie'}))
def sufficientInfo(self): def sufficientInfo(self):
@@ -244,10 +230,10 @@ class Movie(Video):
return True return True
def moveLocation(self): def wantedFilePath(self):
title = titlecase(self.title) title = titlecase(self.title)
parent = '{} ({})'.format(title, self.year) 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): def __repr__(self):
if self.year is None: if self.year is None: