Merge pull request #2 from KevinMidboe/refactor

Refactor
This commit is contained in:
2018-10-16 23:11:45 +02:00
committed by GitHub
20 changed files with 1149 additions and 521 deletions

View File

@@ -16,4 +16,29 @@ There are some settings that need to be set for seasonedParser to be able to fin
### Download directory <a name='download-directory'></a>
In your download client set a incomplete folder and a complete directory. This will allow seasonedParser to only parse items that have been completely downloaded.
*TODO:* Monitor multiple folders at the same time.
*TODO:* Monitor multiple folders at the same time.
## Run
There are many run commands for this, but here is a list of the current working run commands for this project.
```bash
user@host:$ ~/seasonedParser/./seasonedMover.py move 'The.Big.Bang.Theory.S11E(7..14).720p.x264.mkv' '/mnt/mainframe/shows/The Big Bang Theory/The Big Bang Theory S11E'
```
Here the first parameter is our move command, which in turn calls motherMover. The second parameter is what we want the filenames to be called. Notice the (num1..num2), this is to create a range for all the episodes we want to move. The last parameter is the path we want to move our content.
> This will be done automatically by the parser based on the info in the media items name, but it is nice to have a manual command.
## Cli
Arguments
* Dry run with --dry
* Path variable
* daemon with -d option
* Still need the path variable
* Daemon sends confirmation and on missing asks tweetf or correction
Functions
* Should ask for input when missing info, always when cli

View File

@@ -564,4 +564,7 @@ Total: 5926, missed was: 29
real 2m0.766s
user 1m41.482s
sys 0m0.851s
```
```
Keep nfo files?

View File

@@ -1,2 +1,6 @@
guessit==2.1.4
tvdb_api==2.0
hashids==1.2.0
enzyme>=0.4.1
click>=6.7
langdetect>=1.0.7

View File

@@ -1,267 +0,0 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-08-25 23:22:27
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-09-29 12:35:24
from guessit import guessit
import os, errno
import logging
import tvdb_api
from pprint import pprint
import env_variables as env
from video import VIDEO_EXTENSIONS, Episode, Movie, Video
from subtitle import SUBTITLE_EXTENSIONS, Subtitle, get_subtitle_path
from utils import sanitize
logging.basicConfig(filename=env.logfile, level=logging.INFO)
#: Supported archive extensions
ARCHIVE_EXTENSIONS = ('.rar',)
def scan_video(path):
"""Scan a video from a `path`.
:param str path: existing path to the video.
:return: the scanned video.
:rtype: :class:`~subliminal.video.Video`
"""
# check for non-existing path
if not os.path.exists(path):
raise ValueError('Path does not exist')
# check video extension
# if not path.endswith(VIDEO_EXTENSIONS):
# raise ValueError('%r is not a valid video extension' % os.path.splitext(path)[1])
dirpath, filename = os.path.split(path)
logging.info('Scanning video %r in %r', filename, dirpath)
# guess
parent_path = path.strip(filename)
video = Video.fromguess(filename, parent_path, guessit(path))
# video = Video('test')
# guessit(path)
return video
def scan_subtitle(path):
if not os.path.exists(path):
raise ValueError('Path does not exist')
dirpath, filename = os.path.split(path)
logging.info('Scanning subtitle %r in %r', filename, dirpath)
# guess
parent_path = path.strip(filename)
subtitle = Subtitle.fromguess(filename, parent_path, guessit(path))
return subtitle
def scan_files(path, age=None, archives=True):
"""Scan `path` for videos and their subtitles.
See :func:`refine` to find additional information for the video.
:param str path: existing directory path to scan.
:param datetime.timedelta age: maximum age of the video or archive.
:param bool archives: scan videos in archives.
:return: the scanned videos.
:rtype: list of :class:`~subliminal.video.Video`
"""
# check for non-existing path
if not os.path.exists(path):
raise ValueError('Path does not exist')
# check for non-directory path
if not os.path.isdir(path):
raise ValueError('Path is not a directory')
# walk the path
mediafiles = []
for dirpath, dirnames, filenames in os.walk(path):
logging.debug('Walking directory %r', dirpath)
# remove badly encoded and hidden dirnames
for dirname in list(dirnames):
if dirname.startswith('.'):
logging.debug('Skipping hidden dirname %r in %r', dirname, dirpath)
dirnames.remove(dirname)
# scan for videos
for filename in filenames:
# filter on videos and archives
if not (filename.endswith(VIDEO_EXTENSIONS) or filename.endswith(SUBTITLE_EXTENSIONS) or archives and filename.endswith(ARCHIVE_EXTENSIONS)):
continue
# skip hidden files
if filename.startswith('.'):
logging.debug('Skipping hidden filename %r in %r', filename, dirpath)
continue
# reconstruct the file path
filepath = os.path.join(dirpath, filename)
# skip links
if os.path.islink(filepath):
logging.debug('Skipping link %r in %r', filename, dirpath)
continue
# skip old files
if age and datetime.utcnow() - datetime.utcfromtimestamp(os.path.getmtime(filepath)) > age:
logging.debug('Skipping old file %r in %r', filename, dirpath)
continue
# scan
if filename.endswith(VIDEO_EXTENSIONS): # video
try:
video = scan_video(filepath)
mediafiles.append(video)
except ValueError: # pragma: no cover
logging.exception('Error scanning video')
continue
elif archives and filename.endswith(ARCHIVE_EXTENSIONS): # archive
print('archive')
pass
# try:
# video = scan_archive(filepath)
# mediafiles.append(video)
# except (NotRarFile, RarCannotExec, ValueError): # pragma: no cover
# logging.exception('Error scanning archive')
# continue
elif filename.endswith(SUBTITLE_EXTENSIONS): # subtitle
try:
subtitle = scan_subtitle(filepath)
mediafiles.append(subtitle)
except ValueError:
logging.exception('Error scanning subtitle')
continue
else: # pragma: no cover
raise ValueError('Unsupported file %r' % filename)
return mediafiles
def organize_files(path):
hashList = {}
mediafiles = scan_files(path)
# print(mediafiles)
for file in mediafiles:
hashList.setdefault(file.__hash__(),[]).append(file)
# hashList[file.__hash__()] = file
return hashList
def save_subtitles(files, single=False, directory=None, encoding=None):
t = tvdb_api.Tvdb()
if not isinstance(files, list):
files = [files]
for file in files:
# TODO this should not be done in the loop
dirname = "%s S%sE%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode))
createParentfolder = not dirname in file.parent_path
if createParentfolder:
dirname = os.path.join(file.parent_path, dirname)
print('Created: %s' % dirname)
try:
os.makedirs(dirname)
except OSError as e:
if e.errno != errno.EEXIST:
raise
# TODO Clean this !
try:
tvdb_episode = t[file.series][file.season][file.episode]
episode_title = tvdb_episode['episodename']
except:
episode_title = ''
old = os.path.join(file.parent_path, file.name)
if file.name.endswith(SUBTITLE_EXTENSIONS):
lang = file.getLanguage()
sdh = '.sdh' if file.sdh else ''
filename = "%s S%sE%s %s%s.%s.%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode), episode_title, sdh, lang, file.container)
else:
filename = "%s S%sE%s %s.%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode), episode_title, file.container)
if createParentfolder:
newname = os.path.join(dirname, filename)
else:
newname = os.path.join(file.parent_path, filename)
print('Moved: %s ---> %s' % (old, newname))
os.rename(old, newname)
print()
# for hash in files:
# hashIndex = [files[hash]]
# for hashItems in hashIndex:
# for file in hashItems:
# print(file.series)
# saved_subtitles = []
# for subtitle in files:
# # check content
# if subtitle.name is None:
# logging.error('Skipping subtitle %r: no content', subtitle)
# continue
# # check language
# if subtitle.language in set(s.language for s in saved_subtitles):
# logging.debug('Skipping subtitle %r: language already saved', subtitle)
# continue
# # create subtitle path
# subtitle_path = get_subtitle_path(video.name, None if single else subtitle.language)
# if directory is not None:
# subtitle_path = os.path.join(directory, os.path.split(subtitle_path)[1])
# # save content as is or in the specified encoding
# logging.info('Saving %r to %r', subtitle, subtitle_path)
# if encoding is None:
# with io.open(subtitle_path, 'wb') as f:
# f.write(subtitle.content)
# else:
# with io.open(subtitle_path, 'w', encoding=encoding) as f:
# f.write(subtitle.text)
# saved_subtitles.append(subtitle)
# # check single
# if single:
# break
# return saved_subtitles
def main():
# episodePath = '/Volumes/media/tv/Black Mirror/Black Mirror Season 01/'
episodePath = '/media/hdd1/tv/'
t = tvdb_api.Tvdb()
hashList = organize_files(episodePath)
pprint(hashList)
if __name__ == '__main__':
main()

View File

@@ -1 +0,0 @@
logfile = 'conf/output.log'

View File

@@ -1,111 +0,0 @@
# -*- coding: utf-8 -*-
import codecs
import logging
import os
import chardet
import hashlib
from video import Episode, Movie
from utils import sanitize
from langdetect import detect
logger = logging.getLogger(__name__)
#: Subtitle extensions
SUBTITLE_EXTENSIONS = ('.srt', '.sub')
class Subtitle(object):
"""Base class for subtitle.
:param language: language of the subtitle.
:type language: :class:`~babelfish.language.Language`
:param bool hearing_impaired: whether or not the subtitle is hearing impaired.
:param page_link: URL of the web page from which the subtitle can be downloaded.
:type page_link: str
:param encoding: Text encoding of the subtitle.
:type encoding: str
"""
#: Name of the provider that returns that class of subtitle
provider_name = ''
def __init__(self, name, parent_path, series, season, episode, language=None, hash=None, container=None, format=None, sdh=False):
#: Language of the subtitle
self.name = name
self.parent_path = parent_path
self.series = series
self.season = season
self.episode = episode
self.language=language
self.hash = hash
self.container = container
self.format = format
self.sdh = sdh
@classmethod
def fromguess(cls, name, parent_path, guess):
if not (guess['type'] == 'movie' or guess['type'] == 'episode'):
raise ValueError('The guess must be an episode guess')
if 'title' not in guess:
raise ValueError('Insufficient data to process the guess')
sdh = 'sdh' in name.lower()
if guess['type'] is 'episode':
return cls(name, parent_path, guess.get('title', 1), guess.get('season'), guess['episode'],
container=guess.get('container'), format=guess.get('format'), sdh=sdh)
elif guess['type'] is 'movie':
return cls(name, parent_path, guess.get('title', 1), container=guess.get('container'),
format=guess.get('format'), sdh=sdh)
def getLanguage(self):
f = open(os.path.join(self.parent_path, self.name), 'r', encoding='ISO-8859-15')
language = detect(f.read())
f.close()
return language
def __hash__(self):
return hashlib.md5("b'{}'".format(str(self.series) + str(self.season) + str(self.episode)).encode()).hexdigest()
def __repr__(self):
return '<%s %s [%sx%s]>' % (self.__class__.__name__, self.series, self.season, str(self.episode))
def get_subtitle_path(subtitles_path, language=None, extension='.srt'):
"""Get the subtitle path using the `subtitles_path` and `language`.
:param str subtitles_path: path to the subtitle.
:param language: language of the subtitle to put in the path.
:type language: :class:`~babelfish.language.Language`
:param str extension: extension of the subtitle.
:return: path of the subtitle.
:rtype: str
"""
subtitle_root = os.path.splitext(subtitles_path)[0]
if language:
subtitle_root += '.' + str(language)
return subtitle_root + extension

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import hashlib
import os
import re
import struct
def sanitize(string, ignore_characters=None):
"""Sanitize a string to strip special characters.
:param str string: the string to sanitize.
:param set ignore_characters: characters to ignore.
:return: the sanitized string.
:rtype: str
"""
# only deal with strings
if string is None:
return
ignore_characters = ignore_characters or set()
# replace some characters with one space
# characters = {'-', ':', '(', ')', '.'} - ignore_characters
# if characters:
# string = re.sub(r'[%s]' % re.escape(''.join(characters)), ' ', string)
# remove some characters
characters = {'\''} - ignore_characters
if characters:
string = re.sub(r'[%s]' % re.escape(''.join(characters)), '', string)
# replace multiple spaces with one
string = re.sub(r'\s+', ' ', string)
# strip and lower case
return string.strip().lower()

39
src/cli.py Executable file
View File

@@ -0,0 +1,39 @@
#/usr/local/bin/python3
import click
import os
import logging
import env_variables as env
logging.basicConfig(filename=env.logfile, level=logging.INFO)
logger = logging.getLogger('seasonedParser')
fh = logging.FileHandler(env.logfile)
fh.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)
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()

349
src/core.py Executable file
View File

@@ -0,0 +1,349 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-08-25 23:22:27
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-09-29 12:35:24
from guessit import guessit
from babelfish import Language, LanguageReverseError
import hashlib
import os, errno
import shutil
import logging
import re
import tvdb_api
import click
from pprint import pprint
from titlecase import titlecase
import langdetect
import env_variables as env
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.INFO)
logger = logging.getLogger('seasonedParser_core')
fh = logging.FileHandler(env.logfile)
fh.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)
def search_external_subtitles(path, directory=None):
dirpath, filename = os.path.split(path)
dirpath = dirpath or '.'
fileroot, fileext = os.path.splitext(filename)
subtitles = {}
for p in os.listdir(directory or dirpath):
if not p.endswith(SUBTITLE_EXTENSIONS):
continue
language = Language('und')
language_code = p[len(fileroot):-len(os.path.splitext(p)[1])].replace(fileext, '').replace('_','-')[1:]
if language_code:
try:
language = Language.fromietf(language_code)
except (ValueError, LanguageReverseError):
logger.error('Cannot parse language code %r', language_code)
f = open(os.path.join(dirpath, p), 'r', encoding='ISO-8859-15')
pattern = re.compile('[0-9:\,-<>]+')
# head = list(islice(f.read(), 10))
filecontent = pattern.sub('', f.read())
filecontent = filecontent[0:1000]
language = langdetect.detect(filecontent)
f.close()
subtitles[os.path.join(dirpath, p)] = language
logger.debug('Found subtitles %r', subtitles)
return subtitles
def scan_video(path):
"""Scan a video from a `path`.
:param str path: existing path to the video.
:return: the scanned video.
:rtype: :class:`~subliminal.video.Video`
"""
# check for non-existing path
if not os.path.exists(path):
raise ValueError('Path does not exist')
# check video extension
if not path.endswith(VIDEO_EXTENSIONS):
raise ValueError('%r is not a valid video extension' % os.path.splitext(path)[1])
dirpath, filename = os.path.split(path)
logger.info('Scanning video %r in %r', filename, dirpath)
# guess
video = Video.fromguess(path, guessit(path))
# size
video.size = os.path.getsize(path)
# hash of name
# if isinstance(video, Movie):
# if type(video.title) is str and type(video.year) is int:
# home_path = '{} ({})'.format(video.title, video.year)
# hash_str = ''.join([video.title, str(video.year) or ''])
# elif isinstance(video, Episode):
# if type(video.series) is str and type(video.season) is int and type(video.episode) is int:
# home_path = '{} ({})'.format(video.title, video.year)
# hash_str = ''.join([video.series, str(video.season), str(video.episode)])
# video.hash = hashlib.md5(hash_str.encode()).hexdigest()
# except:
# print(video)
return video
def scan_subtitle(path):
if not os.path.exists(path):
raise ValueError('Path does not exist')
dirpath, filename = os.path.split(path)
logger.info('Scanning subtitle %r in %r', filename, dirpath)
# guess
parent_path = path.strip(filename)
subtitle = Subtitle.fromguess(parent_path, guessit(path))
return subtitle
def subtitle_path(sibling, subtitle):
parent_path = os.path.dirname(sibling)
return os.path.join(parent_path, os.path.basename(subtitle))
def scan_videos(path):
"""Scan `path` for videos and their subtitles.
See :func:`refine` to find additional information for the video.
:param str path: existing directory path to scan.
:return: the scanned videos.
:rtype: list of :class:`~subliminal.video.Video`
"""
# check for non-existing path
if not os.path.exists(path):
raise ValueError('Path does not exist')
# check for non-directory path
if not os.path.isdir(path):
raise ValueError('Path is not a directory')
# setup progress bar
path_children = 0
for _ in os.walk(path): path_children += 1
with click.progressbar(length=path_children, show_pos=True, label='Collecting videos') as bar:
# walk the path
videos = []
for dirpath, dirnames, filenames in os.walk(path):
logger.debug('Walking directory %r', dirpath)
# remove badly encoded and hidden dirnames
for dirname in list(dirnames):
if dirname.startswith('.'):
logger.debug('Skipping hidden dirname %r in %r', dirname, dirpath)
dirnames.remove(dirname)
# scan for videos
for filename in filenames:
if not (filename.endswith(VIDEO_EXTENSIONS)):
logger.debug('Skipping non-video file %s', filename)
continue
# skip hidden files
if filename.startswith('.'):
logger.debug('Skipping hidden filename %r in %r', filename, dirpath)
continue
# reconstruct the file path
filepath = os.path.join(dirpath, filename)
if os.path.islink(filepath):
logger.debug('Skipping link %r in %r', filename, dirpath)
continue
# scan
if filename.endswith(VIDEO_EXTENSIONS): # video
try:
video = scan_video(filepath)
except ValueError: # pragma: no cover
logger.exception('Error scanning video')
continue
else: # pragma: no cover
raise ValueError('Unsupported file %r' % filename)
videos.append(video)
bar.update(1)
return videos
def organize_files(path):
hashList = {}
mediafiles = scan_files(path)
# print(mediafiles)
for file in mediafiles:
hashList.setdefault(file.__hash__(),[]).append(file)
# hashList[file.__hash__()] = file
return hashList
def save_subtitles(files, single=False, directory=None, encoding=None):
t = tvdb_api.Tvdb()
if not isinstance(files, list):
files = [files]
for file in files:
# TODO this should not be done in the loop
dirname = "%s S%sE%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode))
createParentfolder = not dirname in file.parent_path
if createParentfolder:
dirname = os.path.join(file.parent_path, dirname)
print('Created: %s' % dirname)
try:
os.makedirs(dirname)
except OSError as e:
if e.errno != errno.EEXIST:
raise
# TODO Clean this !
try:
tvdb_episode = t[file.series][file.season][file.episode]
episode_title = tvdb_episode['episodename']
except:
episode_title = ''
old = os.path.join(file.parent_path, file.name)
if file.name.endswith(SUBTITLE_EXTENSIONS):
lang = file.getLanguage()
sdh = '.sdh' if file.sdh else ''
filename = "%s S%sE%s %s%s.%s.%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode), episode_title, sdh, lang, file.container)
else:
filename = "%s S%sE%s %s.%s" % (file.series, "%02d" % (file.season), "%02d" % (file.episode), episode_title, file.container)
if createParentfolder:
newname = os.path.join(dirname, filename)
else:
newname = os.path.join(file.parent_path, filename)
print('Moved: %s ---> %s' % (old, newname))
os.rename(old, newname)
def scan_folder(path):
videos = []
ignored_videos = []
errored_paths = []
logger.debug('Collecting path %s', path)
# non-existing
if not os.path.exists(path):
try:
video = Video.fromname(path)
except:
logger.exception('Unexpected error while collecting non-existing path %s', path)
errored_paths.append(path)
video.subtitles |= set(search_external_subtitles(video.name, directory=path))
refine(video)
videos.append(video)
# Increment bar to full ?
# directories
if os.path.isdir(path):
try:
scanned_videos = scan_videos(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.style(str(len(videos)), bold=True, fg='green' if videos else None),
's' if len(videos) > 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
def pickforgirlscouts(video):
if video.sufficientInfo():
video.moveLocation()
return True
return False
def moveHome(video):
dir = os.path.dirname(video.home)
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)
for sub in video.subtitles:
if not os.path.isfile(sub):
continue
oldpath = sub
newpath = subtitle_path(video.home, 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()

3
src/env_variables.py Normal file
View File

@@ -0,0 +1,3 @@
logfile = 'conf/output.log'
MOVIEBASE = '/mnt/mainframe/movies'
SHOWBASE = '/mnt/mainframe/shows'

View File

@@ -3,7 +3,7 @@
# @Author: KevinMidboe
# @Date: 2017-10-12 11:55:03
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-10-17 00:58:24
# @Last Modified time: 2017-11-01 16:11:30
import sys, logging, re
from urllib import parse, request
@@ -28,39 +28,6 @@ RELEASE_TYPES = ('bdremux', 'brremux', 'remux',
'camrip', 'cam')
def sanitize(string, ignore_characters=None, replace_characters=None):
"""Sanitize a string to strip special characters.
:param str string: the string to sanitize.
:param set ignore_characters: characters to ignore.
:return: the sanitized string.
:rtype: str
"""
# only deal with strings
if string is None:
return
replace_characters = replace_characters or ''
ignore_characters = ignore_characters or set()
characters = ignore_characters
if characters:
string = re.sub(r'[%s]' % re.escape(''.join(characters)), replace_characters, string)
return string
def return_re_match(string, re_statement):
if string is None:
return
m = re.search(re_statement, string)
if 'Y-day' in m.group():
return datetime.datetime.now().strftime('%m-%d %Y')
return sanitize(m.group(), '\xa0', ' ')
# Should maybe not be able to set values without checking if they are valid?
class piratebay(object):
def __init__(self, query=None, page=0, sort=None, category=None):
@@ -157,7 +124,7 @@ class piratebay(object):
print(self.page)
# Fetch in parallel
n = self.total_pages
n = pagesToCount(multiple_pages, self.total_pages)
while n > 1:
torrents_found.extend(self.next_page())
n -= 1
@@ -276,7 +243,7 @@ def chooseCandidate(torrent_list):
size, _, size_id = torrent.size.partition(' ')
if intersecting_release_types and int(torrent.seed_count) > 0 and float(size) > 4 and size_id == 'GiB':
print('{} : {} : {}'.format(torrent.name, torrent.size, torrent.seed_count))
print('{} : {} : {} {}'.format(torrent.name, torrent.size, torrent.seed_count, torrent.magnet))
interesting_torrents.append(torrent)
# else:
# print('Denied match! %s : %s : %s' % (torrent.name, torrent.size, torrent.seed_count))
@@ -286,10 +253,11 @@ def chooseCandidate(torrent_list):
def searchTorrentSite(query, site='piratebay'):
pirate = piratebay()
torrents_found = pirate.search(query, page=0, multiple_pages=0, sort='size')
# pprint(torrents_found)
torrents_found = pirate.search(query, page=0, multiple_pages=5, sort='size')
pprint(torrents_found)
candidates = chooseCandidate(torrents_found)
pprint(candidates)
exit(0)
torrents_found = pirate.search(query, page=0, multiple_pages=0, sort='size', category='movies')
movie_candidates = chooseCandidate(torrents_found)
@@ -308,4 +276,4 @@ def main():
searchTorrentSite(query)
if __name__ == '__main__':
main()
main()

175
src/scandir.py Executable file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-10-02 16:29:25
# @Last Modified by: KevinMidboe
# @Last Modified time: 2018-01-15 17:18:36
try:
from os import scandir
except ImportError:
from scandir import scandir # use scandir PyPI module on Python < 3.5
import env_variables as env
import multiprocessing as mp
import logging, re, datetime
from guessit import guessit
from video import VIDEO_EXTENSIONS, Episode, Movie, Video
from subtitle import SUBTITLE_EXTENSIONS, Subtitle, get_subtitle_path
logging.basicConfig(filename=env.logfile, level=logging.INFO)
""" Move to utils file """
def removeLeadingZero(number):
stringedNumber = str(number)
if (len(stringedNumber) > 1 and stringedNumber[0] == '0'):
return int(stringedNumber[1:])
return int(number)
class movie(object):
def __init__(self, path, title=None, year=None):
self.path = path
self.title = title
self.year = year
class Episode(object):
def __init__(self, path, name, title=None, season=None, episode=None):
super(Episode, self).__init__()
self.path = path
self.name = name
self.title = title
self.season = season
self.episode = episode
@classmethod
def fromname(cls, path, name):
title = cls.findTitle(cls, name)
season = cls.findSeasonNumber(cls, name)
episode = cls.findEpisodeNumber(cls, name)
return cls(path, name, title, season, episode)
def findTitle(self, name):
m = re.search("([a-zA-Z0-9\'\.\-\ ])+([sS][0-9]{1,3})", name)
if m:
return re.sub('[\ \.]*[sS][0-9]{1,2}', '', m.group(0))
def findSeasonNumber(self, name):
m = re.search('[sS][0-9]{1,2}', name)
if m:
seasonNumber = re.sub('[sS]', '', m.group(0))
return removeLeadingZero(seasonNumber)
def findEpisodeNumber(self, name):
m = re.search('[eE][0-9]{1,3}', name)
if m:
episodeNumber = re.sub('[eE]', '', m.group(0))
return removeLeadingZero(episodeNumber)
def get_tree_size(path):
"""Return total size of files in given path and subdirs."""
total = 0
for entry in scandir(path):
if not ('.DS_Store' in entry.path or 'lost+found' in entry.path):
if entry.is_dir(follow_symlinks=False):
total += get_tree_size(entry.path)
else:
total += entry.stat(follow_symlinks=False).st_size
return int(total)
def scantree(path):
"""Recursively yield DirEntry objects for given directory."""
for entry in scandir(path):
# Skip .DS_Store and lost+found
# TODO have a blacklist here
if not ('.DS_Store' in entry.path or 'lost+found' in entry.path):
if entry.is_dir(follow_symlinks=False):
yield from scantree(entry.path)
else:
yield entry
# Find all the mediaobjects for a given path
# TODO handle list of path's
def get_objects_for_path(path, archives=None, match=False):
# Declare list to save the media objects found in the given path
hashList = {}
mediaFiles = []
# All entries given from scantree functoin
for entry in scantree(path):
logging.debug('Looking at file %s', str(entry.name))
name = entry.name # Pull out name for faster index
# Skip if not corrent media extension
if not (name.endswith(VIDEO_EXTENSIONS) or name.endswith(SUBTITLE_EXTENSIONS) or archives and name.endswith(ARCHIVE_EXTENSIONS)):
continue
# Skip if the file is a dotfile
if name.startswith('.'):
logging.debug('Skipping hidden file %s' % str(name))
continue
# If we have a video, create a class and append to mediaFiles
if name.endswith(VIDEO_EXTENSIONS): # video
episode = Episode.fromname(entry.path, entry.name)
if (episode.title is None):
logging.debug('None found for %s' % name)
continue
title = re.sub('[\.]', ' ', episode.title)
mediaFiles.append(episode)
return mediaFiles
if __name__ == '__main__':
logging.info('Started: %s' % str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f")))
import sys
from pprint import pprint
total = 0
missed = 0
# print(get_tree_size(sys.argv[1] if len(sys.argv) > 1 else '.'))
# print(str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f")))
path = sys.argv[1] if len(sys.argv) > 1 else '.'
mediaFiles = get_objects_for_path(path)
getTitle = lambda ep: ep.title
for ep in mediaFiles:
print(getTitle(ep))
mediaList = []
for entry in scantree(sys.argv[1] if len(sys.argv) > 1 else '.'):
name = entry.name
manual = Episode.fromname(entry.path, entry.name)
size = int(entry.stat(follow_symlinks=False).st_size) / 1024 / 1024 / 1024
# print(name + ' : ' + str(round(size, 2)) + 'GB')
title = manual.title
if title is None:
logging.debug('None found for %s' % (name))
continue
title = re.sub('[\.]', ' ', manual.title)
# try:
# print(name + ' : ' + "%s S%iE%i" % (str(title), manual.season, manual.episode))
# except TypeError:
# logging.error('Unexpected error: ' + name)
mediaList.append(manual)
if ('-m' in sys.argv):
guess = guessit(name)
logging.info('Manual is: {} and guess is {}'.format(title, guess['title']))
# # if not (guess['season'] == manual.season and guess['episode'] == manual.episode):
if (guess['title'].lower() != title.lower()):
logging.info('Missmatch: %s by manual guess: %s : %s' % (name, guess['title'], title))
missed += 1
total += 1
print('Total: %i, missed was: %i' % (total, missed))
logging.info('Ended: %s' % str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f")))
logging.info(' - - - - - - - - - ')

296
src/subtitle.py Normal file
View File

@@ -0,0 +1,296 @@
# -*- coding: utf-8 -*-
import codecs
import logging
import os
import chardet
import hashlib
from video import Episode, Movie
from utils import sanitize
# from langdetect import detect
logger = logging.getLogger(__name__)
#: Subtitle extensions
SUBTITLE_EXTENSIONS = ('.srt')
class Subtitle(object):
"""Base class for subtitle.
:param language: language of the subtitle.
:type language: :class:`~babelfish.language.Language`
:param bool hearing_impaired: whether or not the subtitle is hearing impaired.
:param page_link: URL of the web page from which the subtitle can be downloaded.
:type page_link: str
:param encoding: Text encoding of the subtitle.
:type encoding: str
"""
#: Name of the provider that returns that class of subtitle
provider_name = ''
def __init__(self, language, hearing_impaired=False, page_link=None, encoding=None):
#: Language of the subtitle
self.language = language
#: Whether or not the subtitle is hearing impaired
self.hearing_impaired = hearing_impaired
#: URL of the web page from which the subtitle can be downloaded
self.page_link = page_link
#: Content as bytes
self.content = None
#: Encoding to decode with when accessing :attr:`text`
self.encoding = None
# validate the encoding
if encoding:
try:
self.encoding = codecs.lookup(encoding).name
except (TypeError, LookupError):
logger.debug('Unsupported encoding %s', encoding)
@property
def id(self):
"""Unique identifier of the subtitle"""
raise NotImplementedError
@property
def text(self):
"""Content as string
If :attr:`encoding` is None, the encoding is guessed with :meth:`guess_encoding`
"""
if not self.content:
return
if self.encoding:
return self.content.decode(self.encoding, errors='replace')
return self.content.decode(self.guess_encoding(), errors='replace')
def is_valid(self):
"""Check if a :attr:`text` is a valid SubRip format.
:return: whether or not the subtitle is valid.
:rtype: bool
"""
if not self.text:
return False
try:
pysrt.from_string(self.text, error_handling=pysrt.ERROR_RAISE)
except pysrt.Error as e:
if e.args[0] < 80:
return False
return True
def guess_encoding(self):
"""Guess encoding using the language, falling back on chardet.
:return: the guessed encoding.
:rtype: str
"""
logger.info('Guessing encoding for language %s', self.language)
# always try utf-8 first
encodings = ['utf-8']
# add language-specific encodings
if self.language.alpha3 == 'zho':
encodings.extend(['gb18030', 'big5'])
elif self.language.alpha3 == 'jpn':
encodings.append('shift-jis')
elif self.language.alpha3 == 'ara':
encodings.append('windows-1256')
elif self.language.alpha3 == 'heb':
encodings.append('windows-1255')
elif self.language.alpha3 == 'tur':
encodings.extend(['iso-8859-9', 'windows-1254'])
elif self.language.alpha3 == 'pol':
# Eastern European Group 1
encodings.extend(['windows-1250'])
elif self.language.alpha3 == 'bul':
# Eastern European Group 2
encodings.extend(['windows-1251'])
else:
# Western European (windows-1252)
encodings.append('latin-1')
# try to decode
logger.debug('Trying encodings %r', encodings)
for encoding in encodings:
try:
self.content.decode(encoding)
except UnicodeDecodeError:
pass
else:
logger.info('Guessed encoding %s', encoding)
return encoding
logger.warning('Could not guess encoding from language')
# fallback on chardet
encoding = chardet.detect(self.content)['encoding']
logger.info('Chardet found encoding %s', encoding)
return encoding
def get_matches(self, video):
"""Get the matches against the `video`.
:param video: the video to get the matches with.
:type video: :class:`~subliminal.video.Video`
:return: matches of the subtitle.
:rtype: set
"""
raise NotImplementedError
def __hash__(self):
return hash(self.provider_name + '-' + self.id)
def __repr__(self):
return '<%s %r [%s]>' % (self.__class__.__name__, self.id, self.language)
def get_subtitle_path(video_path, language=None, extension='.srt'):
"""Get the subtitle path using the `video_path` and `language`.
:param str video_path: path to the video.
:param language: language of the subtitle to put in the path.
:type language: :class:`~babelfish.language.Language`
:param str extension: extension of the subtitle.
:return: path of the subtitle.
:rtype: str
"""
subtitle_root = os.path.splitext(video_path)[0]
if language:
subtitle_root += '.' + str(language)
return subtitle_root + extension
def guess_matches(video, guess, partial=False):
"""Get matches between a `video` and a `guess`.
If a guess is `partial`, the absence information won't be counted as a match.
:param video: the video.
:type video: :class:`~subliminal.video.Video`
:param guess: the guess.
:type guess: dict
:param bool partial: whether or not the guess is partial.
:return: matches between the `video` and the `guess`.
:rtype: set
"""
matches = set()
if isinstance(video, Episode):
# series
if video.series and 'title' in guess and sanitize(guess['title']) == sanitize(video.series):
matches.add('series')
# title
if video.title and 'episode_title' in guess and sanitize(guess['episode_title']) == sanitize(video.title):
matches.add('title')
# season
if video.season and 'season' in guess and guess['season'] == video.season:
matches.add('season')
# episode
if video.episode and 'episode' in guess and guess['episode'] == video.episode:
matches.add('episode')
# year
if video.year and 'year' in guess and guess['year'] == video.year:
matches.add('year')
# count "no year" as an information
if not partial and video.original_series and 'year' not in guess:
matches.add('year')
elif isinstance(video, Movie):
# year
if video.year and 'year' in guess and guess['year'] == video.year:
matches.add('year')
# title
if video.title and 'title' in guess and sanitize(guess['title']) == sanitize(video.title):
matches.add('title')
# release_group
if (video.release_group and 'release_group' in guess and
sanitize_release_group(guess['release_group']) in
get_equivalent_release_groups(sanitize_release_group(video.release_group))):
matches.add('release_group')
# resolution
if video.resolution and 'screen_size' in guess and guess['screen_size'] == video.resolution:
matches.add('resolution')
# format
if video.format and 'format' in guess and guess['format'].lower() == video.format.lower():
matches.add('format')
# video_codec
if video.video_codec and 'video_codec' in guess and guess['video_codec'] == video.video_codec:
matches.add('video_codec')
# audio_codec
if video.audio_codec and 'audio_codec' in guess and guess['audio_codec'] == video.audio_codec:
matches.add('audio_codec')
return matches
def fix_line_ending(content):
"""Fix line ending of `content` by changing it to \n.
:param bytes content: content of the subtitle.
:return: the content with fixed line endings.
:rtype: bytes
"""
return content.replace(b'\r\n', b'\n').replace(b'\r', b'\n')
'''
@classmethod
def fromguess(cls, name, parent_path, guess):
if not (guess['type'] == 'movie' or guess['type'] == 'episode'):
raise ValueError('The guess must be an episode guess')
if 'title' not in guess:
raise ValueError('Insufficient data to process the guess')
sdh = 'sdh' in name.lower()
if guess['type'] is 'episode':
return cls(name, parent_path, guess.get('title', 1), guess.get('season'), guess['episode'],
container=guess.get('container'), format=guess.get('format'), sdh=sdh)
elif guess['type'] is 'movie':
return cls(name, parent_path, guess.get('title', 1), container=guess.get('container'),
format=guess.get('format'), sdh=sdh)
def getLanguage(self):
f = open(os.path.join(self.parent_path, self.name), 'r', encoding='ISO-8859-15')
language = detect(f.read())
f.close()
return language
def __hash__(self):
return hashlib.md5("b'{}'".format(str(self.series) + str(self.season) + str(self.episode)).encode()).hexdigest()
def __repr__(self):
return '<%s %s [%sx%s]>' % (self.__class__.__name__, self.series, self.season, str(self.episode))
def get_subtitle_path(subtitles_path, language=None, extension='.srt'):
"""Get the subtitle path using the `subtitles_path` and `language`.
:param str subtitles_path: path to the subtitle.
:param language: language of the subtitle to put in the path.
:type language: :class:`~babelfish.language.Language`
:param str extension: extension of the subtitle.
:return: path of the subtitle.
:rtype: str
"""
subtitle_root = os.path.splitext(subtitles_path)[0]
if language:
subtitle_root += '.' + str(language)
return subtitle_root + extension
'''

135
src/utils.py Normal file
View File

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import hashlib
import os
import logging
import re
import struct
from babelfish import Error as BabelfishError, Language
from enzyme import MalformedMKVError, MKV
def sanitize(string, ignore_characters=None):
"""Sanitize a string to strip special characters.
:param str string: the string to sanitize.
:param set ignore_characters: characters to ignore.
:return: the sanitized string.
:rtype: str
"""
# only deal with strings
if string is None:
return
ignore_characters = ignore_characters or set()
# replace some characters with one space
# characters = {'-', ':', '(', ')', '.'} - ignore_characters
# if characters:
# string = re.sub(r'[%s]' % re.escape(''.join(characters)), ' ', string)
# remove some characters
characters = {'\''} - ignore_characters
if characters:
string = re.sub(r'[%s]' % re.escape(''.join(characters)), '', string)
# replace multiple spaces with one
string = re.sub(r'\s+', ' ', string)
# strip and lower case
return string.strip().lower()
def refine(video, embedded_subtitles=True, **kwargs):
"""Refine a video by searching its metadata.
Several :class:`~subliminal.video.Video` attributes can be found:
* :attr:`~subliminal.video.Video.resolution`
* :attr:`~subliminal.video.Video.video_codec`
* :attr:`~subliminal.video.Video.audio_codec`
* :attr:`~subliminal.video.Video.embeded_subtitles`
:param bool embedded_subtitles: search for embedded subtitles.
"""
# skip non existing videos
if not video.exists:
return
# check extensions
extension = os.path.splitext(video.name)[1]
if extension == '.mkv':
with open(video.name, 'rb') as f:
try:
mkv = MKV(f)
except MalformedMKVError:
logging.error('Failed to parse mkv, malformed file')
return
except KeyError:
logging.error('Key error while opening file, uncompatible mkv container')
return
# main video track
if mkv.video_tracks:
video_track = mkv.video_tracks[0]
# resolution
if video_track.height in (480, 720, 1080, 2160):
if video_track.interlaced:
video.resolution = '%di' % video_track.height
else:
video.resolution = '%dp' % video_track.height
logging.debug('Found resolution %s', video.resolution)
# video codec
if video_track.codec_id == 'V_MPEG4/ISO/AVC':
video.video_codec = 'h264'
logging.debug('Found video_codec %s', video.video_codec)
elif video_track.codec_id == 'V_MPEG4/ISO/SP':
video.video_codec = 'DivX'
logging.debug('Found video_codec %s', video.video_codec)
elif video_track.codec_id == 'V_MPEG4/ISO/ASP':
video.video_codec = 'XviD'
logging.debug('Found video_codec %s', video.video_codec)
else:
logging.warning('MKV has no video track')
# main audio track
if mkv.audio_tracks:
audio_track = mkv.audio_tracks[0]
# audio codec
if audio_track.codec_id == 'A_AC3':
video.audio_codec = 'AC3'
logging.debug('Found audio_codec %s', video.audio_codec)
elif audio_track.codec_id == 'A_DTS':
video.audio_codec = 'DTS'
logging.debug('Found audio_codec %s', video.audio_codec)
elif audio_track.codec_id == 'A_AAC':
video.audio_codec = 'AAC'
logging.debug('Found audio_codec %s', video.audio_codec)
else:
logging.warning('MKV has no audio track')
# subtitle tracks
if mkv.subtitle_tracks:
if embedded_subtitles:
embeded_subtitles = set()
for st in mkv.subtitle_tracks:
if st.language:
try:
embeded_subtitles.add(Language.fromalpha3b(st.language))
except BabelfishError:
logging.error('Embedded subtitle track language %r is not a valid language', st.language)
embeded_subtitles.add(Language('und'))
elif st.name:
try:
embeded_subtitles.add(Language.fromname(st.name))
except BabelfishError:
logging.debug('Embedded subtitle track name %r is not a valid language', st.name)
embeded_subtitles.add(Language('und'))
else:
embeded_subtitles.add(Language('und'))
logging.debug('Found embedded subtitle %r', embeded_subtitles)
video.embeded_subtitles |= embeded_subtitles
else:
logging.debug('MKV has no subtitle track')
else:
logging.debug('Unsupported video extension %s', extension)

View File

@@ -3,16 +3,22 @@
# @Author: KevinMidboe
# @Date: 2017-08-26 08:23:18
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-09-29 13:56:21
# @Last Modified time: 2018-05-13 20:50:00
from guessit import guessit
import os
import logging
from titlecase import titlecase
import hashlib, tvdb_api
import env_variables as env
logger = logging.getLogger('seasonedParser_core')
#: Video extensions
VIDEO_EXTENSIONS = ('.3g2', '.3gp', '.3gp2', '.3gpp', '.60d', '.ajp', '.asf', '.asx', '.avchd', '.avi', '.bik',
'.bix', '.box', '.cam', '.dat', '.divx', '.dmf', '.dv', '.dvr-ms', '.evo', '.flc', '.fli',
'.flic', '.flv', '.flx', '.gvi', '.gvp', '.h264', '.m1v', '.m2p', '.m2ts', '.m2v', '.m4e',
'.flic', '.flv', '.flx', '.gvi', '.gvp', '.h264', '.m1v', '.m2p', '.m2v', '.m4e',
'.m4v', '.mjp', '.mjpeg', '.mjpg', '.mkv', '.moov', '.mov', '.movhd', '.movie', '.movx', '.mp4',
'.mpe', '.mpeg', '.mpg', '.mpv', '.mpv2', '.mxf', '.nsv', '.nut', '.ogg', '.ogm' '.ogv', '.omf',
'.ps', '.qt', '.ram', '.rm', '.rmvb', '.swf', '.ts', '.vfw', '.vid', '.video', '.viv', '.vivo',
@@ -24,19 +30,25 @@ class Video(object):
:param str name: name or path of the video.
:param str format: format of the video (HDTV, WEB-DL, BluRay, ...).
:param str release_group: release group of the video.
:param str resolution: resolution of the video stream (480p, 720p, 1080p or 1080i).
: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 imdb_id: IMDb id of the video.
:param dict hashes: hashes of the video file by provider names.
:param str home: optimal parent folder.
:param dict name_hash: hashes of the video file by provider names.
:param int size: size of the video file in bytes.
:param set subtitle_languages: existing subtitle languages.
:param set subtitles: existing subtitle languages.
"""
def __init__(self, name, format=None, release_group=None, resolution=None, video_codec=None, audio_codec=None,
imdb_id=None, hashes=None, size=None, subtitle_languages=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):
#: Name or path of the video
self.name = name
#: Hashes of the video file by provider names
self.hash = hash
#: Size of the video file in bytes
self.size = size
#: Format of the video (HDTV, WEB-DL, BluRay, ...)
self.format = format
@@ -52,17 +64,14 @@ class Video(object):
#: Codec of the main audio stream
self.audio_codec = audio_codec
#: IMDb id of the video
self.imdb_id = imdb_id
#: Hashes of the video file by provider names
self.hashes = hashes or {}
#: Size of the video file in bytes
self.size = size
#: optimal home path; parent folder.
self.home = home
#: Existing subtitle languages
self.subtitle_languages = subtitle_languages or set()
self.subtitles = subtitles or set()
#: Embeded subtitle languages
self.embeded_subtitles = embeded_subtitles or set()
@property
def exists(self):
@@ -78,14 +87,14 @@ class Video(object):
return timedelta()
@classmethod
def fromguess(cls, name, parent_path, guess):
def fromguess(cls, name, guess):
"""Create an :class:`Episode` or a :class:`Movie` with the given `name` based on the `guess`.
:param str name: name of the video.
:param dict guess: guessed data.
:raise: :class:`ValueError` if the `type` of the `guess` is invalid
"""
if guess['type'] == 'episode':
return Episode.fromguess(name, parent_path, guess)
return Episode.fromguess(name, guess)
if guess['type'] == 'movie':
return Movie.fromguess(name, guess)
@@ -106,7 +115,7 @@ class Video(object):
return hash(self.name)
class Episode():
class Episode(Video):
"""Episode :class:`Video`.
:param str series: series of the episode.
:param int season: season number of the episode.
@@ -117,14 +126,9 @@ class Episode():
:param int tvdb_id: TVDB id of the episode.
:param \*\*kwargs: additional parameters for the :class:`Video` constructor.
"""
def __init__(self, name, parent_path, series, season, episode, year=None, original_series=True, tvdb_id=None,
series_tvdb_id=None, series_imdb_id=None, release_group=None, video_codec=None, container=None,
format=None, screen_size=None, **kwargs):
super(Episode, self).__init__()
self.name = name
self.parent_path = parent_path
def __init__(self, name, series, season, episode, title=None, year=None, original_series=True, tvdb_id=None,
series_tvdb_id=None, **kwargs):
super(Episode, self).__init__(name, **kwargs)
#: Series of the episode
self.series = series
@@ -135,6 +139,9 @@ class Episode():
#: Episode number of the episode
self.episode = episode
#: Title of the episode
self.title = title
#: Year of series
self.year = year
@@ -147,68 +154,66 @@ class Episode():
#: TVDB id of the series
self.series_tvdb_id = series_tvdb_id
#: IMDb id of the series
self.series_imdb_id = series_imdb_id
# The release group of the episode
self.release_group = release_group
# The video vodec of the series
self.video_codec = video_codec
# The Video container of the episode
self.container = container
# The Video format of the episode
self.format = format
# The Video screen_size of the episode
self.screen_size = screen_size
@classmethod
def fromguess(cls, name, parent_path, guess):
def fromguess(cls, name, guess):
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')
return cls(name, parent_path, guess['title'], guess.get('season', 1), guess['episode'],
year=guess.get('year'), original_series='year' not in guess, release_group=guess.get('release_group'),
video_codec=guess.get('video_codec'), audio_codec=guess.get('audio_codec'), container=guess.get('container'),
format=guess.get('format'), screen_size=guess.get('screen_size'))
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,
release_group=guess.get('release_group'), resolution=guess.get('screen_size'),
video_codec=guess.get('video_codec'), audio_codec=guess.get('audio_codec'))
@classmethod
def fromname(cls, name):
return cls.fromguess(name, guessit(name, {'type': 'episode'}))
def __hash__(self):
return hashlib.md5("b'{}'".format(str(self.series) + str(self.season) + str(self.episode)).encode()).hexdigest()
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):
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))
# THE EP NUMBER IS CONVERTED TO STRING AS A QUICK FIX FOR MULTIPLE NUMBERS IN ONE
def __repr__(self):
if self.year is None:
return '<%s [%r, %sx%s]>' % (self.__class__.__name__, self.series, self.season, str(self.episode))
return '<%s [%r, %dx%s]>' % (self.__class__.__name__, self.series, self.season, self.episode)
if self.subtitles is not None and len(self.subtitles) > 0:
return '<%s [%r, %dx%s] %s>' % (self.__class__.__name__, self.series, self.season, self.episode, self.subtitles)
return '<%s [%r, %d, %sx%s]>' % (self.__class__.__name__, self.series, self.year, self.season, str(self.episode))
return '<%s [%r, %d, %dx%d]>' % (self.__class__.__name__, self.series, self.year, self.season, self.episode)
class Movie():
class Movie(Video):
"""Movie :class:`Video`.
:param str title: title of the movie.
:param int year: year of the movie.
:param \*\*kwargs: additional parameters for the :class:`Video` constructor.
"""
def __init__(self, name, title, year=None, format=None, **kwargs):
super(Movie, self).__init__()
def __init__(self, name, title, year=None, **kwargs):
super(Movie, self).__init__(name, **kwargs)
#: Title of the movie
self.title = title
#: Year of the movie
self.year = year
self.format = format
@classmethod
def fromguess(cls, name, guess):
@@ -226,6 +231,24 @@ class Movie():
def fromname(cls, name):
return cls.fromguess(name, guessit(name, {'type': 'movie'}))
def sufficientInfo(self):
t = hasattr(self, "title")
y = hasattr(self, "year")
if False in [t, y] or None in [self.title, self.year]:
logger.error('{} or {} found to have none value, manual correction required'.format(self.title, self.year))
return False
if list in [type(self.title), type(self.year)]:
logger.error('{} or {} found to have list value, manual correction required'.format(self.title, self.year))
return False
return True
def moveLocation(self):
title = titlecase(self.title)
parent = '{} ({})'.format(title, self.year)
self.home = os.path.join(env.MOVIEBASE, parent, os.path.basename(self.name))
def __repr__(self):
if self.year is None:
return '<%s [%r]>' % (self.__class__.__name__, self.title)

25
src/walk.py Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
# @Author: KevinMidboe
# @Date: 2017-10-02 16:29:25
# @Last Modified by: KevinMidboe
# @Last Modified time: 2017-10-02 18:07:26
import itertools, os
import multiprocessing
def worker(filename):
print(filename)
def main():
with multiprocessing.Pool(48) as Pool: # pool of 48 processes
walk = os.walk("/Volumes/mainframe/shows/")
fn_gen = itertools.chain.from_iterable((os.path.join(root, file)
for file in files)
for root, dirs, files in walk)
results_of_work = Pool.map(worker, fn_gen) # this does the parallel processing
if __name__ == '__main__':
main()