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 Hachoir library 1.3.3 to 1.3.4 (r1383)
* 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)

View file

@ -230,6 +230,45 @@ class Quality:
else:
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
def assumeQuality(name):
if name.lower().endswith(('.avi', '.mp4')):
@ -262,10 +301,19 @@ class Quality:
@staticmethod
def statusFromName(name, assume=True, anime=False):
quality = Quality.nameQuality(name, anime)
if assume and quality == Quality.UNKNOWN:
if assume and Quality.UNKNOWN == quality:
quality = Quality.assumeQuality(name)
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
SNATCHED = None
SNATCHED_PROPER = None

View file

@ -662,7 +662,7 @@ class PostProcessor(object):
continue
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 common.Quality.UNKNOWN != ep_quality:
@ -671,6 +671,12 @@ class PostProcessor(object):
else:
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
ep_quality = common.Quality.assumeQuality(self.file_name)
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,
convert=True).parse(
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):
# If the filename doesn't parse, then return false as last
# resort. We can assume that unparseable filenames are not

View file

@ -613,141 +613,137 @@ class TVShow(object):
def makeEpFromFile(self, 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
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:
myParser = NameParser(showObj=self, try_indexers=True)
parse_result = myParser.parse(file)
my_parser = NameParser(showObj=self, try_indexers=True)
parse_result = my_parser.parse(file)
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
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
if not len(parse_result.episode_numbers):
logger.log("parse_result: " + str(parse_result))
logger.log(u"No episode number found in " + file + ", ignoring it", logger.ERROR)
logger.log(u'parse_result: %s' % parse_result)
logger.log(u'No episode number found in %s, ignoring it' % file, logger.ERROR)
return None
# 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
rootEp = None
root_ep = None
sql_l = []
for curEpNum in episodes:
for cur_ep_num in episodes:
episode = int(curEpNum)
episode = int(cur_ep_num)
logger.log(
str(self.indexerid) + ": " + file + " parsed to " + self.name + " " + str(season) + "x" + str(episode),
logger.DEBUG)
logger.log(u'%s: %s parsed to %s %sx%s' % (self.indexerid, file, self.name, season, episode), logger.DEBUG)
checkQualityAgain = False
check_quality_again = False
same_file = False
curEp = self.getEpisode(season, episode)
cur_ep = self.getEpisode(season, episode)
if curEp == None:
if None is cur_ep:
try:
curEp = self.getEpisode(season, episode, file)
cur_ep = self.getEpisode(season, episode, file)
except exceptions.EpisodeNotFoundException:
logger.log(str(self.indexerid) + u": Unable to figure out what this file is, skipping",
logger.ERROR)
logger.log(u'%s: Unable to figure out what this file is, skipping' % self.indexerid, logger.ERROR)
continue
else:
# 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(
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)
checkQualityAgain = True
check_quality_again = True
with curEp.lock:
old_size = curEp.file_size
curEp.location = file
with cur_ep.lock:
old_size = cur_ep.file_size
cur_ep.location = 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
else:
same_file = False
curEp.checkForMetaFiles()
cur_ep.checkForMetaFiles()
if rootEp == None:
rootEp = curEp
if None is root_ep:
root_ep = cur_ep
else:
if curEp not in rootEp.relatedEps:
rootEp.relatedEps.append(curEp)
if cur_ep not in root_ep.relatedEps:
root_ep.relatedEps.append(cur_ep)
# if it's a new file then
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 checkQualityAgain and not same_file:
newQuality = Quality.nameQuality(file, self.is_anime)
logger.log(u"Since this file has been renamed, I checked " + file + " and found quality " +
Quality.qualityStrings[newQuality], logger.DEBUG)
if newQuality != Quality.UNKNOWN:
curEp.status = Quality.compositeStatus(DOWNLOADED, newQuality)
if check_quality_again and not same_file:
new_quality = Quality.nameQuality(file, self.is_anime)
if Quality.UNKNOWN == new_quality:
new_quality = Quality.fileQuality(file)
logger.log(u'Since this file was renamed, file %s was checked and quality "%s" found'
% (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
elif not same_file and sickbeard.helpers.isMediaFile(file) and curEp.status not in Quality.DOWNLOADED + [
ARCHIVED, IGNORED]:
elif not same_file and sickbeard.helpers.isMediaFile(file)\
and cur_ep.status not in Quality.DOWNLOADED + [ARCHIVED, IGNORED]:
oldStatus, oldQuality = Quality.splitCompositeStatus(curEp.status)
newQuality = Quality.nameQuality(file, self.is_anime)
if newQuality == Quality.UNKNOWN:
newQuality = Quality.assumeQuality(file)
old_status, old_quality = Quality.splitCompositeStatus(cur_ep.status)
new_quality = Quality.nameQuality(file, self.is_anime)
if Quality.UNKNOWN == new_quality:
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 oldStatus == SNATCHED and oldQuality <= newQuality:
logger.log(u"STATUS: this episode used to be snatched with quality " + Quality.qualityStrings[
oldQuality] + u" but a file exists with quality " + Quality.qualityStrings[
newQuality] + u" so I'm setting the status to DOWNLOADED", logger.DEBUG)
newStatus = DOWNLOADED
if SNATCHED == old_status and old_quality <= new_quality:
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'
% (Quality.qualityStrings[old_quality], Quality.qualityStrings[new_quality]), logger.DEBUG)
new_status = DOWNLOADED
# if it was snatched proper and we found a higher quality one then allow the status change
elif oldStatus == SNATCHED_PROPER and oldQuality < newQuality:
logger.log(u"STATUS: this episode used to be snatched proper with quality " + Quality.qualityStrings[
oldQuality] + u" but a file exists with quality " + Quality.qualityStrings[
newQuality] + u" so I'm setting the status to DOWNLOADED", logger.DEBUG)
newStatus = DOWNLOADED
elif SNATCHED_PROPER == old_status and old_quality < new_quality:
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'
% (Quality.qualityStrings[old_quality], Quality.qualityStrings[new_quality]), logger.DEBUG)
new_status = DOWNLOADED
elif oldStatus not in (SNATCHED, SNATCHED_PROPER):
newStatus = DOWNLOADED
elif old_status not in (SNATCHED, SNATCHED_PROPER):
new_status = DOWNLOADED
if newStatus != None:
with curEp.lock:
logger.log(u"STATUS: we have an associated file, so setting the status from " + str(
curEp.status) + u" to DOWNLOADED/" + str(Quality.statusFromName(file, anime=self.is_anime)),
logger.DEBUG)
curEp.status = Quality.compositeStatus(newStatus, newQuality)
if None is not new_status:
with cur_ep.lock:
logger.log(u'STATUS: we have an associated file, so setting the status from %s to DOWNLOADED/%s'
% (cur_ep.status, Quality.compositeStatus(Quality.DOWNLOADED, new_quality)), logger.DEBUG)
cur_ep.status = Quality.compositeStatus(new_status, new_quality)
with curEp.lock:
result = curEp.get_sql()
with cur_ep.lock:
result = cur_ep.get_sql()
if None is not result:
sql_l.append(result)
if 0 < len(sql_l):
myDB = db.DBConnection()
myDB.mass_action(sql_l)
my_db = db.DBConnection()
my_db.mass_action(sql_l)
# creating metafiles on the root should be good enough
if sickbeard.USE_FAILED_DOWNLOADS and rootEp is not None:
with rootEp.lock:
rootEp.createMetaFiles()
if sickbeard.USE_FAILED_DOWNLOADS and root_ep is not None:
with root_ep.lock:
root_ep.createMetaFiles()
return rootEp
return root_ep
def loadFromDB(self, skipNFO=False):
@ -1812,14 +1808,13 @@ class TVEpisode(object):
elif sickbeard.helpers.isMediaFile(self.location):
# 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]:
logger.log(
u"5 Status changes from " + str(self.status) + " to " + str(Quality.statusFromName(self.location)),
logger.DEBUG)
self.status = Quality.statusFromName(self.location, anime=self.show.is_anime)
status_quality = Quality.statusFromNameOrFile(self.location, anime=self.show.is_anime)
logger.log(u'(1) Status changes from %s to %s' % (self.status, status_quality), logger.DEBUG)
self.status = status_quality
# shouldn't get here probably
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
def loadFromNFO(self, location):
@ -1837,11 +1832,10 @@ class TVEpisode(object):
if self.location != "":
if self.status == UNKNOWN:
if sickbeard.helpers.isMediaFile(self.location):
logger.log(u"7 Status changes from " + str(self.status) + " to " + str(
Quality.statusFromName(self.location, anime=self.show.is_anime)), logger.DEBUG)
self.status = Quality.statusFromName(self.location, anime=self.show.is_anime)
if UNKNOWN == self.status and sickbeard.helpers.isMediaFile(self.location):
status_quality = Quality.statusFromNameOrFile(self.location, anime=self.show.is_anime)
logger.log(u'(3) Status changes from %s to %s' % (self.status, status_quality), logger.DEBUG)
self.status = status_quality
nfoFile = sickbeard.helpers.replaceExtension(self.location, "nfo")
logger.log(str(self.show.indexerid) + u": Using NFO name " + nfoFile, logger.DEBUG)