Add parse media content to determine quality.

Determine quality before making final assumptions during re-scan, force update, pp and other processes.
Add a postprocess folder name validation.
This commit is contained in:
JackDandy 2015-08-21 03:32:27 +01:00
parent 92bf6bb6d6
commit 70a8b47b44
5 changed files with 141 additions and 87 deletions

View file

@ -17,6 +17,8 @@
* Update Beautiful Soup 4.3.2 to 4.4.0 (r390) * Update Beautiful Soup 4.3.2 to 4.4.0 (r390)
* Update Hachoir library 1.3.3 to 1.3.4 (r1383) * Update Hachoir library 1.3.3 to 1.3.4 (r1383)
* Change configure quiet option in Hachoir to suppress warnings (add ref:hacks.txt) * Change configure quiet option in Hachoir to suppress warnings (add ref:hacks.txt)
* Add parse media content to determine quality before making final assumptions during re-scan, update, pp
* Add a postprocess folder name validation
### 0.10.0 (2015-08-06 11:05:00 UTC) ### 0.10.0 (2015-08-06 11:05:00 UTC)

View file

@ -230,6 +230,45 @@ class Quality:
else: else:
return Quality.UNKNOWN return Quality.UNKNOWN
@staticmethod
def fileQuality(filename):
from sickbeard import encodingKludge as ek
if ek.ek(os.path.isfile, filename):
from hachoir_parser import createParser
from hachoir_metadata import extractMetadata
from hachoir_core.stream import InputStreamError
parser = height = None
try:
parser = createParser(filename)
except InputStreamError as e:
logger.log('Hachoir can\'t parse file content quality because it found error: %s' % e.text, logger.WARNING)
if parser:
extract = extractMetadata(parser)
if extract:
try:
height = extract.get('height')
except (AttributeError, ValueError):
try:
for metadata in extract.iterGroups():
if re.search('(?i)video', metadata.header):
height = metadata.get('height')
break
except (AttributeError, ValueError):
pass
parser.stream._input.close()
tolerance = lambda value, percent: int(round(value - (value * percent / 100.0)))
if height >= tolerance(352, 5):
if height <= tolerance(720, 2):
return Quality.SDTV
return (Quality.HDTV, Quality.FULLHDTV)[height >= tolerance(1080, 1)]
return Quality.UNKNOWN
@staticmethod @staticmethod
def assumeQuality(name): def assumeQuality(name):
if name.lower().endswith(('.avi', '.mp4')): if name.lower().endswith(('.avi', '.mp4')):
@ -262,10 +301,19 @@ class Quality:
@staticmethod @staticmethod
def statusFromName(name, assume=True, anime=False): def statusFromName(name, assume=True, anime=False):
quality = Quality.nameQuality(name, anime) quality = Quality.nameQuality(name, anime)
if assume and quality == Quality.UNKNOWN: if assume and Quality.UNKNOWN == quality:
quality = Quality.assumeQuality(name) quality = Quality.assumeQuality(name)
return Quality.compositeStatus(DOWNLOADED, quality) return Quality.compositeStatus(DOWNLOADED, quality)
@staticmethod
def statusFromNameOrFile(file_path, assume=True, anime=False):
quality = Quality.nameQuality(file_path, anime)
if Quality.UNKNOWN == quality:
quality = Quality.fileQuality(file_path)
if assume and Quality.UNKNOWN == quality:
quality = Quality.assumeQuality(file_path)
return Quality.compositeStatus(DOWNLOADED, quality)
DOWNLOADED = None DOWNLOADED = None
SNATCHED = None SNATCHED = None
SNATCHED_PROPER = None SNATCHED_PROPER = None

View file

@ -662,7 +662,7 @@ class PostProcessor(object):
continue continue
ep_quality = common.Quality.nameQuality(cur_name, ep_obj.show.is_anime) ep_quality = common.Quality.nameQuality(cur_name, ep_obj.show.is_anime)
quality_log = u' "%s" quality from the %s %s' % (common.Quality.qualityStrings[ep_quality], thing, cur_name) quality_log = u' "%s" quality parsed from the %s %s' % (common.Quality.qualityStrings[ep_quality], thing, cur_name)
# if we find a good one then use it # if we find a good one then use it
if common.Quality.UNKNOWN != ep_quality: if common.Quality.UNKNOWN != ep_quality:
@ -671,6 +671,12 @@ class PostProcessor(object):
else: else:
self._log(u'Found' + quality_log, logger.DEBUG) self._log(u'Found' + quality_log, logger.DEBUG)
ep_quality = common.Quality.fileQuality(self.file_path)
if common.Quality.UNKNOWN != ep_quality:
self._log(u'Using "%s" quality parsed from the metadata file content of %s'
% (common.Quality.qualityStrings[ep_quality], self.file_name), logger.DEBUG)
return ep_quality
# Try guessing quality from the file name # Try guessing quality from the file name
ep_quality = common.Quality.assumeQuality(self.file_name) ep_quality = common.Quality.assumeQuality(self.file_name)
self._log(u'Using guessed "%s" quality from the file name %s' self._log(u'Using guessed "%s" quality from the file name %s'

View file

@ -434,6 +434,10 @@ class ProcessTVShow(object):
try_scene_exceptions=True, try_scene_exceptions=True,
convert=True).parse( convert=True).parse(
dir_name, cache_result=False) dir_name, cache_result=False)
# check we parsed id, ep and season
if not (0 < len(parse_result.episode_numbers) and isinstance(parse_result.show.indexerid, int)
and isinstance(parse_result.season_number, int)):
return False
except (InvalidNameException, InvalidShowException): except (InvalidNameException, InvalidShowException):
# If the filename doesn't parse, then return false as last # If the filename doesn't parse, then return false as last
# resort. We can assume that unparseable filenames are not # resort. We can assume that unparseable filenames are not

View file

@ -613,141 +613,137 @@ class TVShow(object):
def makeEpFromFile(self, file): def makeEpFromFile(self, file):
if not ek.ek(os.path.isfile, file): if not ek.ek(os.path.isfile, file):
logger.log(str(self.indexerid) + u": That isn't even a real file dude... " + file) logger.log(u'%s: Not a real file... %s' % (self.indexerid, file))
return None return None
logger.log(str(self.indexerid) + u": Creating episode object from " + file, logger.DEBUG) logger.log(u'%s: Creating episode object from %s' % (self.indexerid, file), logger.DEBUG)
try: try:
myParser = NameParser(showObj=self, try_indexers=True) my_parser = NameParser(showObj=self, try_indexers=True)
parse_result = myParser.parse(file) parse_result = my_parser.parse(file)
except InvalidNameException: except InvalidNameException:
logger.log(u"Unable to parse the filename " + file + " into a valid episode", logger.DEBUG) logger.log(u'Unable to parse the filename %s into a valid episode' % file, logger.DEBUG)
return None return None
except InvalidShowException: except InvalidShowException:
logger.log(u"Unable to parse the filename " + file + " into a valid show", logger.DEBUG) logger.log(u'Unable to parse the filename %s into a valid show' % file, logger.DEBUG)
return None return None
if not len(parse_result.episode_numbers): if not len(parse_result.episode_numbers):
logger.log("parse_result: " + str(parse_result)) logger.log(u'parse_result: %s' % parse_result)
logger.log(u"No episode number found in " + file + ", ignoring it", logger.ERROR) logger.log(u'No episode number found in %s, ignoring it' % file, logger.ERROR)
return None return None
# for now lets assume that any episode in the show dir belongs to that show # for now lets assume that any episode in the show dir belongs to that show
season = parse_result.season_number if parse_result.season_number != None else 1 season = parse_result.season_number if None is not parse_result.season_number else 1
episodes = parse_result.episode_numbers episodes = parse_result.episode_numbers
rootEp = None root_ep = None
sql_l = [] sql_l = []
for curEpNum in episodes: for cur_ep_num in episodes:
episode = int(curEpNum) episode = int(cur_ep_num)
logger.log( logger.log(u'%s: %s parsed to %s %sx%s' % (self.indexerid, file, self.name, season, episode), logger.DEBUG)
str(self.indexerid) + ": " + file + " parsed to " + self.name + " " + str(season) + "x" + str(episode),
logger.DEBUG)
checkQualityAgain = False check_quality_again = False
same_file = False same_file = False
curEp = self.getEpisode(season, episode) cur_ep = self.getEpisode(season, episode)
if curEp == None: if None is cur_ep:
try: try:
curEp = self.getEpisode(season, episode, file) cur_ep = self.getEpisode(season, episode, file)
except exceptions.EpisodeNotFoundException: except exceptions.EpisodeNotFoundException:
logger.log(str(self.indexerid) + u": Unable to figure out what this file is, skipping", logger.log(u'%s: Unable to figure out what this file is, skipping' % self.indexerid, logger.ERROR)
logger.ERROR)
continue continue
else: else:
# if there is a new file associated with this ep then re-check the quality # if there is a new file associated with this ep then re-check the quality
if curEp.location and ek.ek(os.path.normpath, curEp.location) != ek.ek(os.path.normpath, file): if cur_ep.location and ek.ek(os.path.normpath, cur_ep.location) != ek.ek(os.path.normpath, file):
logger.log( logger.log(
u"The old episode had a different file associated with it, I will re-check the quality based on the new filename " + file, u'The old episode had a different file associated with it, re-checking the quality based on the new filename ' + file,
logger.DEBUG) logger.DEBUG)
checkQualityAgain = True check_quality_again = True
with curEp.lock: with cur_ep.lock:
old_size = curEp.file_size old_size = cur_ep.file_size
curEp.location = file cur_ep.location = file
# if the sizes are the same then it's probably the same file # if the sizes are the same then it's probably the same file
if old_size and curEp.file_size == old_size: if old_size and cur_ep.file_size == old_size:
same_file = True same_file = True
else: else:
same_file = False same_file = False
curEp.checkForMetaFiles() cur_ep.checkForMetaFiles()
if rootEp == None: if None is root_ep:
rootEp = curEp root_ep = cur_ep
else: else:
if curEp not in rootEp.relatedEps: if cur_ep not in root_ep.relatedEps:
rootEp.relatedEps.append(curEp) root_ep.relatedEps.append(cur_ep)
# if it's a new file then # if it's a new file then
if not same_file: if not same_file:
curEp.release_name = '' cur_ep.release_name = ''
# if they replace a file on me I'll make some attempt at re-checking the quality unless I know it's the same file # if they replace a file on me I'll make some attempt at re-checking the quality unless I know it's the same file
if checkQualityAgain and not same_file: if check_quality_again and not same_file:
newQuality = Quality.nameQuality(file, self.is_anime) new_quality = Quality.nameQuality(file, self.is_anime)
logger.log(u"Since this file has been renamed, I checked " + file + " and found quality " + if Quality.UNKNOWN == new_quality:
Quality.qualityStrings[newQuality], logger.DEBUG) new_quality = Quality.fileQuality(file)
if newQuality != Quality.UNKNOWN: logger.log(u'Since this file was renamed, file %s was checked and quality "%s" found'
curEp.status = Quality.compositeStatus(DOWNLOADED, newQuality) % (file, Quality.qualityStrings[new_quality]), logger.DEBUG)
if Quality.UNKNOWN != new_quality:
cur_ep.status = Quality.compositeStatus(DOWNLOADED, new_quality)
# check for status/quality changes as long as it's a new file # check for status/quality changes as long as it's a new file
elif not same_file and sickbeard.helpers.isMediaFile(file) and curEp.status not in Quality.DOWNLOADED + [ elif not same_file and sickbeard.helpers.isMediaFile(file)\
ARCHIVED, IGNORED]: and cur_ep.status not in Quality.DOWNLOADED + [ARCHIVED, IGNORED]:
oldStatus, oldQuality = Quality.splitCompositeStatus(curEp.status) old_status, old_quality = Quality.splitCompositeStatus(cur_ep.status)
newQuality = Quality.nameQuality(file, self.is_anime) new_quality = Quality.nameQuality(file, self.is_anime)
if newQuality == Quality.UNKNOWN: if Quality.UNKNOWN == new_quality:
newQuality = Quality.assumeQuality(file) new_quality = Quality.fileQuality(file)
if Quality.UNKNOWN == new_quality:
new_quality = Quality.assumeQuality(file)
newStatus = None new_status = None
# if it was snatched and now exists then set the status correctly # if it was snatched and now exists then set the status correctly
if oldStatus == SNATCHED and oldQuality <= newQuality: if SNATCHED == old_status and old_quality <= new_quality:
logger.log(u"STATUS: this episode used to be snatched with quality " + Quality.qualityStrings[ logger.log(u'STATUS: this episode used to be snatched with quality %s but a file exists with quality %s so setting the status to DOWNLOADED'
oldQuality] + u" but a file exists with quality " + Quality.qualityStrings[ % (Quality.qualityStrings[old_quality], Quality.qualityStrings[new_quality]), logger.DEBUG)
newQuality] + u" so I'm setting the status to DOWNLOADED", logger.DEBUG) new_status = DOWNLOADED
newStatus = DOWNLOADED
# if it was snatched proper and we found a higher quality one then allow the status change # if it was snatched proper and we found a higher quality one then allow the status change
elif oldStatus == SNATCHED_PROPER and oldQuality < newQuality: elif SNATCHED_PROPER == old_status and old_quality < new_quality:
logger.log(u"STATUS: this episode used to be snatched proper with quality " + Quality.qualityStrings[ logger.log(u'STATUS: this episode used to be snatched proper with quality %s but a file exists with quality %s so setting the status to DOWNLOADED'
oldQuality] + u" but a file exists with quality " + Quality.qualityStrings[ % (Quality.qualityStrings[old_quality], Quality.qualityStrings[new_quality]), logger.DEBUG)
newQuality] + u" so I'm setting the status to DOWNLOADED", logger.DEBUG) new_status = DOWNLOADED
newStatus = DOWNLOADED
elif oldStatus not in (SNATCHED, SNATCHED_PROPER): elif old_status not in (SNATCHED, SNATCHED_PROPER):
newStatus = DOWNLOADED new_status = DOWNLOADED
if newStatus != None: if None is not new_status:
with curEp.lock: with cur_ep.lock:
logger.log(u"STATUS: we have an associated file, so setting the status from " + str( logger.log(u'STATUS: we have an associated file, so setting the status from %s to DOWNLOADED/%s'
curEp.status) + u" to DOWNLOADED/" + str(Quality.statusFromName(file, anime=self.is_anime)), % (cur_ep.status, Quality.compositeStatus(Quality.DOWNLOADED, new_quality)), logger.DEBUG)
logger.DEBUG) cur_ep.status = Quality.compositeStatus(new_status, new_quality)
curEp.status = Quality.compositeStatus(newStatus, newQuality)
with curEp.lock: with cur_ep.lock:
result = curEp.get_sql() result = cur_ep.get_sql()
if None is not result: if None is not result:
sql_l.append(result) sql_l.append(result)
if 0 < len(sql_l): if 0 < len(sql_l):
myDB = db.DBConnection() my_db = db.DBConnection()
myDB.mass_action(sql_l) my_db.mass_action(sql_l)
# creating metafiles on the root should be good enough # creating metafiles on the root should be good enough
if sickbeard.USE_FAILED_DOWNLOADS and rootEp is not None: if sickbeard.USE_FAILED_DOWNLOADS and root_ep is not None:
with rootEp.lock: with root_ep.lock:
rootEp.createMetaFiles() root_ep.createMetaFiles()
return rootEp return root_ep
def loadFromDB(self, skipNFO=False): def loadFromDB(self, skipNFO=False):
@ -1812,14 +1808,13 @@ class TVEpisode(object):
elif sickbeard.helpers.isMediaFile(self.location): elif sickbeard.helpers.isMediaFile(self.location):
# leave propers alone, you have to either post-process them or manually change them back # leave propers alone, you have to either post-process them or manually change them back
if self.status not in Quality.SNATCHED_PROPER + Quality.DOWNLOADED + Quality.SNATCHED + [ARCHIVED]: if self.status not in Quality.SNATCHED_PROPER + Quality.DOWNLOADED + Quality.SNATCHED + [ARCHIVED]:
logger.log( status_quality = Quality.statusFromNameOrFile(self.location, anime=self.show.is_anime)
u"5 Status changes from " + str(self.status) + " to " + str(Quality.statusFromName(self.location)), logger.log(u'(1) Status changes from %s to %s' % (self.status, status_quality), logger.DEBUG)
logger.DEBUG) self.status = status_quality
self.status = Quality.statusFromName(self.location, anime=self.show.is_anime)
# shouldn't get here probably # shouldn't get here probably
else: else:
logger.log(u"6 Status changes from " + str(self.status) + " to " + str(UNKNOWN), logger.DEBUG) logger.log(u"(2) Status changes from " + str(self.status) + " to " + str(UNKNOWN), logger.DEBUG)
self.status = UNKNOWN self.status = UNKNOWN
def loadFromNFO(self, location): def loadFromNFO(self, location):
@ -1837,11 +1832,10 @@ class TVEpisode(object):
if self.location != "": if self.location != "":
if self.status == UNKNOWN: if UNKNOWN == self.status and sickbeard.helpers.isMediaFile(self.location):
if sickbeard.helpers.isMediaFile(self.location): status_quality = Quality.statusFromNameOrFile(self.location, anime=self.show.is_anime)
logger.log(u"7 Status changes from " + str(self.status) + " to " + str( logger.log(u'(3) Status changes from %s to %s' % (self.status, status_quality), logger.DEBUG)
Quality.statusFromName(self.location, anime=self.show.is_anime)), logger.DEBUG) self.status = status_quality
self.status = Quality.statusFromName(self.location, anime=self.show.is_anime)
nfoFile = sickbeard.helpers.replaceExtension(self.location, "nfo") nfoFile = sickbeard.helpers.replaceExtension(self.location, "nfo")
logger.log(str(self.show.indexerid) + u": Using NFO name " + nfoFile, logger.DEBUG) logger.log(str(self.show.indexerid) + u": Using NFO name " + nfoFile, logger.DEBUG)