mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-07 02:23:38 +00:00
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:
parent
92bf6bb6d6
commit
70a8b47b44
5 changed files with 141 additions and 87 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
164
sickbeard/tv.py
164
sickbeard/tv.py
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue