Merge pull request #484 from JackDandy/feature/ChangeSceneNumbers

Change reduce aggressive use of scene numbering that was overriding u…
This commit is contained in:
JackDandy 2015-08-18 23:11:36 +01:00
commit 41e1119f69
19 changed files with 222 additions and 205 deletions

View file

@ -1,6 +1,14 @@
### 0.11.0 (2015-xx-xx xx:xx:xx UTC)
* Change to only refresh scene exception data for shows that need it
* Change reduce aggressive use of scene numbering that was overriding user preference where not needed
* Change set "Scene numbering" checkbox and add text to the label tip in third step of add "New Show" if scene numbers
are found for the selected show in the search results of the first step
* Change label text on edit show page to highlight when manual numbering and scene numbers are available
* Fix disabling "Scene numbering" of step three in add "New Show" was ignored when scene episode number mappings exist
* Fix don't use scene episode number mappings everywhere when "Scene numbering" is disabled for a show
* Fix width of legend underlining on the third step used to bring other display elements into alignment
* Change when downloading magnet or nzb files, verify the file in cache dir and then move to blackhole
* Fix small cosmetic issue to correctly display "full backlog" date
* Add search crawler exclusions
* Fix saving default show list group on add new show options page

View file

@ -921,7 +921,7 @@ home_newShow.tmpl
#newShowPortal,
fieldset.sectionwrap,
div.formpaginate{
width:801px
width:831px
}
#addShowForm{
@ -1666,6 +1666,10 @@ td.col-search{
padding:15px 0 0
}
#addShowForm #editShow.stepDiv span.component-desc{
width:639px
}
/* =======================================================================
episodeView.tmpl
========================================================================== */
@ -3486,7 +3490,7 @@ div.stepsguide{
div.stepsguide .step{
float:left;
width:267px;
width:277px;
font:bold 24px Arial
}

View file

@ -186,7 +186,7 @@
<span class="component-title">Scene numbering</span>
<span class="component-desc">
<input type="checkbox" name="scene" id="scene"#if $show.scene == 1 then $html_checked else ''#>
<p>search for episodes that are numbered by scene groups instead of by the TV network</p>
<p>search for episodes numbered by scene groups instead of by the TV network <em class="grey-text">(#if $show_has_scene_map then 'scene/manual numbers' else 'manual numbers only '# available)</em></p>
</span>
</label>
</div>

View file

@ -10,6 +10,10 @@
#import os.path
#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl')
<script>
var show_scene_maps = ${show_scene_maps}
</script>
<script type="text/javascript" src="$sbRoot/js/formwizard.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/qualityChooser.js?$sbPID"></script>
<script type="text/javascript" src="$sbRoot/js/newShow.js?$sbPID"></script>

View file

@ -79,7 +79,7 @@
<span class="component-title">Scene numbering</span>
<span class="component-desc">
<input type="checkbox" name="scene" id="scene" #if $sickbeard.SCENE_DEFAULT then "checked=\"checked\"" else ""# />
<p>search for episodes that are numbered by scene groups instead of by the TV network</p>
<p>search for episodes numbered by scene groups instead of by the TV network<span id="scene-maps-found" style="display:none" class="grey-text"> (scene numbers found)</span></p>
</span>
</label>
</div>

View file

@ -90,7 +90,7 @@
$('#SubMenu a:contains("Backlog Overview")').addClass('btn').html('<i class="sgicon-backlog"></i>Backlog Overview');
$('#SubMenu a[href$="/home/updatePLEX/"]').addClass('btn').html('<i class="sgicon-plex"></i>Update PLEX');
$('#SubMenu a:contains("Force")').addClass('btn').html('<i class="sgicon-fullupdate"></i>Force Full Update');
$('#SubMenu a:contains("Rename")').addClass('btn').html('<i class="sgicon-rename"></i>Preview Rename');
$('#SubMenu a:contains("Rename")').addClass('btn').html('<i class="sgicon-rename"></i>Media Renamer');
$('#SubMenu a[href$="/config/subtitles/"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Search Subtitles');
$('#SubMenu a[href*="/home/subtitleShow"]').addClass('btn').html('<i class="sgicon-subtitles"></i>Download Subtitles');
$('#SubMenu a:contains("Anime")').addClass('btn').html('<i class="sgicon-anime"></i>Anime');

View file

@ -189,22 +189,23 @@ $(document).ready(function () {
function updateSampleText() {
// if something's selected then we have some behavior to figure out
var show_name,
var show_name = '',
sep_char,
elRadio = $('input:radio[name="whichSeries"]:checked'),
elInput = $('input:hidden[name="whichSeries"]'),
elScene = $('#scene'),
elRootDirs = $('#rootDirs'),
elFullShowPath = $('#fullShowPath');
// if they've picked a radio button then use that
if (elRadio.length) {
show_name = elRadio.val().split('|')[4];
elScene[0].checked = 0 <= show_scene_maps.indexOf(parseInt(elRadio.val().split('|')[3], 10));
$('#scene-maps-found').css('display', elScene.is(':checked') ? 'inline' : 'None');
}
// if we provided a show in the hidden field, use that
else if (elInput.length && elInput.val().length) {
show_name = $('#providedName').val();
} else {
show_name = '';
}
update_bwlist(show_name);
var sample_text = '<p>Adding show <span class="show-name">' + cleanseText(show_name, !0) + '</span>'

View file

@ -27,7 +27,6 @@ import regexes
import sickbeard
from sickbeard import logger, helpers, scene_numbering, common, scene_exceptions, encodingKludge as ek, db
from dateutil import parser
from sickbeard.exceptions import ex
from sickbeard.common import cpu_presets
@ -55,39 +54,40 @@ class NameParser(object):
else:
self._compile_regexes(self.ALL_REGEX)
def clean_series_name(self, series_name):
@staticmethod
def clean_series_name(series_name):
"""Cleans up series name by removing any . and _
characters, along with any trailing hyphens.
Is basically equivalent to replacing all _ and . with a
space, but handles decimal numbers in string, for example:
>>> cleanRegexedSeriesName("an.example.1.0.test")
>>> clean_series_name('an.example.1.0.test')
'an example 1.0 test'
>>> cleanRegexedSeriesName("an_example_1.0_test")
>>> clean_series_name('an_example_1.0_test')
'an example 1.0 test'
Stolen from dbr's tvnamer
"""
series_name = re.sub("(\D)\.(?!\s)(\D)", "\\1 \\2", series_name)
series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot
series_name = re.sub("(\D)\.(?!\s)", "\\1 ", series_name)
series_name = re.sub("\.(?!\s)(\D)", " \\1", series_name)
series_name = series_name.replace("_", " ")
series_name = re.sub("-$", "", series_name)
series_name = re.sub("^\[.*\]", "", series_name)
series_name = re.sub('(\D)\.(?!\s)(\D)', '\\1 \\2', series_name)
series_name = re.sub('(\d)\.(\d{4})', '\\1 \\2', series_name) # if it ends in a year then don't keep the dot
series_name = re.sub('(\D)\.(?!\s)', '\\1 ', series_name)
series_name = re.sub('\.(?!\s)(\D)', ' \\1', series_name)
series_name = series_name.replace('_', ' ')
series_name = re.sub('-$', '', series_name)
series_name = re.sub('^\[.*\]', '', series_name)
return series_name.strip()
def _compile_regexes(self, regexMode):
if regexMode == self.ANIME_REGEX:
logger.log(u"Using ANIME regexs", logger.DEBUG)
if self.ANIME_REGEX == regexMode:
logger.log(u'Using ANIME regexs', logger.DEBUG)
uncompiled_regex = [regexes.anime_regexes]
elif regexMode == self.NORMAL_REGEX:
logger.log(u"Using NORMAL regexs", logger.DEBUG)
elif self.NORMAL_REGEX == regexMode:
logger.log(u'Using NORMAL regexs', logger.DEBUG)
uncompiled_regex = [regexes.normal_regexes]
else:
logger.log(u"Using ALL regexes", logger.DEBUG)
logger.log(u'Using ALL regexes', logger.DEBUG)
uncompiled_regex = [regexes.normal_regexes, regexes.anime_regexes]
self.compiled_regexes = {0: [], 1: []}
@ -97,7 +97,7 @@ class NameParser(object):
try:
cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE)
except re.error as errormsg:
logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern))
logger.log(u'WARNING: Invalid episode_pattern, %s. %s' % (errormsg, cur_pattern))
else:
self.compiled_regexes[index].append([cur_pattern_num, cur_pattern_name, cur_regex])
index += 1
@ -107,7 +107,7 @@ class NameParser(object):
return
matches = []
bestResult = None
for regex in self.compiled_regexes:
for (cur_regex_num, cur_regex_name, cur_regex) in self.compiled_regexes[regex]:
match = cur_regex.match(name)
@ -132,7 +132,7 @@ class NameParser(object):
if 'season_num' in named_groups:
tmp_season = int(match.group('season_num'))
if cur_regex_name == 'bare' and tmp_season in (19, 20):
if 'bare' == cur_regex_name and tmp_season in (19, 20):
continue
result.season_number = tmp_season
result.score += 1
@ -161,7 +161,7 @@ class NameParser(object):
month = int(match.group('air_month'))
day = int(match.group('air_day'))
# make an attempt to detect YYYY-DD-MM formats
if month > 12:
if 12 < month:
tmp_month = month
month = day
day = tmp_month
@ -174,7 +174,7 @@ class NameParser(object):
tmp_extra_info = match.group('extra_info')
# Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season
if tmp_extra_info and cur_regex_name == 'season_only' and re.search(
if tmp_extra_info and 'season_only' == cur_regex_name and re.search(
r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I):
continue
result.extra_info = tmp_extra_info
@ -198,42 +198,42 @@ class NameParser(object):
if len(matches):
# pick best match with highest score based on placement
bestResult = max(sorted(matches, reverse=True, key=lambda x: x.which_regex), key=lambda x: x.score)
best_result = max(sorted(matches, reverse=True, key=lambda x: x.which_regex), key=lambda x: x.score)
show = None
if not self.naming_pattern:
# try and create a show object for this result
show = helpers.get_show(bestResult.series_name, self.try_indexers, self.try_scene_exceptions)
show = helpers.get_show(best_result.series_name, self.try_indexers, self.try_scene_exceptions)
# confirm passed in show object indexer id matches result show object indexer id
if show and not self.testing:
if self.showObj and show.indexerid != self.showObj.indexerid:
show = None
bestResult.show = show
elif not show and self.showObj:
bestResult.show = self.showObj
show = self.showObj
best_result.show = show
if bestResult.show and bestResult.show.is_anime and len(self.compiled_regexes[1]) > 1 and regex != 1:
if show and show.is_anime and 1 < len(self.compiled_regexes[1]) and 1 != regex:
continue
# if this is a naming pattern test then return best result
if not bestResult.show or self.naming_pattern:
return bestResult
if not show or self.naming_pattern:
return best_result
# get quality
bestResult.quality = common.Quality.nameQuality(name, bestResult.show.is_anime)
best_result.quality = common.Quality.nameQuality(name, show.is_anime)
new_episode_numbers = []
new_season_numbers = []
new_absolute_numbers = []
# if we have an air-by-date show then get the real season/episode numbers
if bestResult.is_air_by_date:
airdate = bestResult.air_date.toordinal()
myDB = db.DBConnection()
sql_result = myDB.select(
"SELECT season, episode FROM tv_episodes WHERE showid = ? and indexer = ? and airdate = ?",
[bestResult.show.indexerid, bestResult.show.indexer, airdate])
if best_result.is_air_by_date:
airdate = best_result.air_date.toordinal()
my_db = db.DBConnection()
sql_result = my_db.select(
'SELECT season, episode FROM tv_episodes WHERE showid = ? and indexer = ? and airdate = ?',
[show.indexerid, show.indexer, airdate])
season_number = None
episode_numbers = []
@ -244,64 +244,64 @@ class NameParser(object):
if not season_number or not len(episode_numbers):
try:
lINDEXER_API_PARMS = sickbeard.indexerApi(bestResult.show.indexer).api_params.copy()
lindexer_api_parms = sickbeard.indexerApi(show.indexer).api_params.copy()
if bestResult.show.lang:
lINDEXER_API_PARMS['language'] = bestResult.show.lang
if show.lang:
lindexer_api_parms['language'] = show.lang
t = sickbeard.indexerApi(bestResult.show.indexer).indexer(**lINDEXER_API_PARMS)
t = sickbeard.indexerApi(show.indexer).indexer(**lindexer_api_parms)
epObj = t[bestResult.show.indexerid].airedOn(bestResult.air_date)[0]
ep_obj = t[show.indexerid].airedOn(best_result.air_date)[0]
season_number = int(epObj["seasonnumber"])
episode_numbers = [int(epObj["episodenumber"])]
season_number = int(ep_obj['seasonnumber'])
episode_numbers = [int(ep_obj['episodenumber'])]
except sickbeard.indexer_episodenotfound:
logger.log(u"Unable to find episode with date " + str(bestResult.air_date) + " for show " + bestResult.show.name + ", skipping", logger.WARNING)
logger.log(u'Unable to find episode with date ' + str(best_result.air_date) + ' for show ' + show.name + ', skipping', logger.WARNING)
episode_numbers = []
except sickbeard.indexer_error as e:
logger.log(u"Unable to contact " + sickbeard.indexerApi(bestResult.show.indexer).name + ": " + ex(e), logger.WARNING)
logger.log(u'Unable to contact ' + sickbeard.indexerApi(show.indexer).name + ': ' + ex(e), logger.WARNING)
episode_numbers = []
for epNo in episode_numbers:
s = season_number
e = epNo
if self.convert:
(s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid,
bestResult.show.indexer,
if self.convert and show.is_scene:
(s, e) = scene_numbering.get_indexer_numbering(show.indexerid,
show.indexer,
season_number,
epNo)
new_episode_numbers.append(e)
new_season_numbers.append(s)
elif bestResult.show.is_anime and len(bestResult.ab_episode_numbers) and not self.testing:
scene_season = scene_exceptions.get_scene_exception_by_name(bestResult.series_name)[1]
for epAbsNo in bestResult.ab_episode_numbers:
elif show.is_anime and len(best_result.ab_episode_numbers) and not self.testing:
scene_season = scene_exceptions.get_scene_exception_by_name(best_result.series_name)[1]
for epAbsNo in best_result.ab_episode_numbers:
a = epAbsNo
if self.convert:
a = scene_numbering.get_indexer_absolute_numbering(bestResult.show.indexerid,
bestResult.show.indexer, epAbsNo,
if self.convert and show.is_scene:
a = scene_numbering.get_indexer_absolute_numbering(show.indexerid,
show.indexer, epAbsNo,
True, scene_season)
(s, e) = helpers.get_all_episodes_from_absolute_number(bestResult.show, [a])
(s, e) = helpers.get_all_episodes_from_absolute_number(show, [a])
new_absolute_numbers.append(a)
new_episode_numbers.extend(e)
new_season_numbers.append(s)
elif bestResult.season_number and len(bestResult.episode_numbers) and not self.testing:
for epNo in bestResult.episode_numbers:
s = bestResult.season_number
elif best_result.season_number and len(best_result.episode_numbers) and not self.testing:
for epNo in best_result.episode_numbers:
s = best_result.season_number
e = epNo
if self.convert:
(s, e) = scene_numbering.get_indexer_numbering(bestResult.show.indexerid,
bestResult.show.indexer,
bestResult.season_number,
if self.convert and show.is_scene:
(s, e) = scene_numbering.get_indexer_numbering(show.indexerid,
show.indexer,
best_result.season_number,
epNo)
if bestResult.show.is_anime:
a = helpers.get_absolute_number_from_season_and_episode(bestResult.show, s, e)
if show.is_anime:
a = helpers.get_absolute_number_from_season_and_episode(show, s, e)
if a:
new_absolute_numbers.append(a)
@ -312,11 +312,11 @@ class NameParser(object):
# from more than one season (by tvdb numbering), and this is just too much
# for sickbeard, so we'd need to flag it.
new_season_numbers = list(set(new_season_numbers)) # remove duplicates
if len(new_season_numbers) > 1:
raise InvalidNameException("Scene numbering results episodes from "
"seasons %s, (i.e. more than one) and "
"SickGear does not support this. "
"Sorry." % (str(new_season_numbers)))
if 1 < len(new_season_numbers):
raise InvalidNameException('Scene numbering results episodes from '
'seasons %s, (i.e. more than one) and '
'SickGear does not support this. '
'Sorry.' % (str(new_season_numbers)))
# I guess it's possible that we'd have duplicate episodes too, so lets
# eliminate them
@ -328,24 +328,24 @@ class NameParser(object):
new_absolute_numbers.sort()
if len(new_absolute_numbers):
bestResult.ab_episode_numbers = new_absolute_numbers
best_result.ab_episode_numbers = new_absolute_numbers
if len(new_season_numbers) and len(new_episode_numbers):
bestResult.episode_numbers = new_episode_numbers
bestResult.season_number = new_season_numbers[0]
best_result.episode_numbers = new_episode_numbers
best_result.season_number = new_season_numbers[0]
if self.convert:
logger.log(
u"Converted parsed result " + bestResult.original_name + " into " + str(bestResult).decode('utf-8',
'xmlcharrefreplace'),
logger.DEBUG)
if self.convert and show.is_scene:
logger.log(u'Converted parsed result %s into %s'
% (best_result.original_name, str(best_result).decode('utf-8', 'xmlcharrefreplace')),
logger.DEBUG)
# CPU sleep
time.sleep(cpu_presets[sickbeard.CPU_PRESET])
return bestResult
return best_result
def _combine_results(self, first, second, attr):
@staticmethod
def _combine_results(first, second, attr):
# if the first doesn't exist then return the second or nothing
if not first:
if not second:
@ -361,19 +361,21 @@ class NameParser(object):
b = getattr(second, attr)
# if a is good use it
if a != None or (type(a) == list and len(a)):
if None is not a or (list == type(a) and len(a)):
return a
# if not use b (if b isn't set it'll just be default)
else:
return b
def _unicodify(self, obj, encoding="utf-8"):
@staticmethod
def _unicodify(obj, encoding='utf-8'):
if isinstance(obj, basestring):
if not isinstance(obj, unicode):
obj = unicode(obj, encoding, 'replace')
return obj
def _convert_number(self, org_number):
@staticmethod
def _convert_number(org_number):
"""
Convert org_number into an integer
org_number: integer or representation of a number: string or unicode
@ -392,8 +394,7 @@ class NameParser(object):
# on error try converting from Roman numerals
roman_to_int_map = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100),
('XC', 90), ('L', 50), ('XL', 40), ('X', 10),
('IX', 9), ('V', 5), ('IV', 4), ('I', 1)
)
('IX', 9), ('V', 5), ('IV', 4), ('I', 1))
roman_numeral = str(org_number).upper()
number = 0
@ -469,19 +470,18 @@ class NameParser(object):
if not final_result.show:
if self.testing:
pass
#final_result.which_regex = []
else:
raise InvalidShowException(
"Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
raise InvalidShowException('Unable to parse %s'
% name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
# if there's no useful info in it then raise an exception
if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and not final_result.ab_episode_numbers and not final_result.series_name:
raise InvalidNameException("Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
if None is final_result.season_number and not final_result.episode_numbers and None is final_result.air_date and not final_result.ab_episode_numbers and not final_result.series_name:
raise InvalidNameException('Unable to parse %s' % name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
if cache_result:
name_parser_cache.add(name, final_result)
logger.log(u"Parsed " + name + " into " + str(final_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG)
logger.log(u'Parsed %s into %s' % (name, str(final_result).decode('utf-8', 'xmlcharrefreplace')), logger.DEBUG)
return final_result
@ -498,8 +498,7 @@ class ParseResult(object):
show=None,
score=None,
quality=None,
version=None
):
version=None):
self.original_name = original_name
@ -549,23 +548,15 @@ class ParseResult(object):
return False
if self.ab_episode_numbers != other.ab_episode_numbers:
return False
#if self.show != other.show:
# return False
#if self.score != other.score:
# return False
#if self.quality != other.quality:
# return False
#if self.version != other.version:
# return False
return True
def __str__(self):
if self.series_name != None:
if None is not self.series_name:
to_return = self.series_name + u' - '
else:
to_return = u''
if self.season_number != None:
if None is not self.season_number:
to_return += 'S' + str(self.season_number)
if self.episode_numbers and len(self.episode_numbers):
for e in self.episode_numbers:
@ -574,17 +565,17 @@ class ParseResult(object):
if self.is_air_by_date:
to_return += str(self.air_date)
if self.ab_episode_numbers:
to_return += ' [ABS: ' + str(self.ab_episode_numbers) + ']'
to_return += ' [ABS: %s]' % str(self.ab_episode_numbers)
if self.is_anime:
if self.version:
to_return += ' [ANIME VER: ' + str(self.version) + ']'
to_return += ' [ANIME VER: %s]' % str(self.version)
if self.release_group:
to_return += ' [GROUP: ' + self.release_group + ']'
to_return += ' [GROUP: %s]' % self.release_group
to_return += ' [ABD: ' + str(self.is_air_by_date) + ']'
to_return += ' [ANIME: ' + str(self.is_anime) + ']'
to_return += ' [whichReg: ' + str(self.which_regex) + ']'
to_return += ' [ABD: %s]' % str(self.is_air_by_date)
to_return += ' [ANIME: %s]' % str(self.is_anime)
to_return += ' [whichReg: %s]' % str(self.which_regex)
return to_return.encode('utf-8')
@ -614,7 +605,7 @@ class NameParserCache(object):
def get(self, name):
if name in self._previous_parsed:
logger.log("Using cached parse result for: " + name, logger.DEBUG)
logger.log('Using cached parse result for: ' + name, logger.DEBUG)
return self._previous_parsed[name]
@ -622,8 +613,8 @@ name_parser_cache = NameParserCache()
class InvalidNameException(Exception):
"The given release name is not valid"
"""The given release name is not valid"""
class InvalidShowException(Exception):
"The given show name is not valid"
"""The given show name is not valid"""

View file

@ -62,7 +62,7 @@ class BeyondHDProvider(generic.TorrentProvider):
for mode in search_params.keys():
if 'Cache' != mode:
show_type = self.show.air_by_date and 'Air By Date' \
or self.show.sports and 'Sports' or self.show.anime and 'Anime' or None
or self.show.is_sports and 'Sports' or self.show.is_anime and 'Anime' or None
if show_type:
logger.log(u'Provider does not carry shows of type: [%s], skipping' % show_type, logger.DEBUG)
return results

View file

@ -171,13 +171,13 @@ class BTNProvider(generic.TorrentProvider):
current_params = {'category': 'Season'}
# Search for entire seasons: no need to do special things for air by date or sports shows
if ep_obj.show.air_by_date or ep_obj.show.sports:
if ep_obj.show.air_by_date or ep_obj.show.is_sports:
# Search for the year of the air by date show
current_params['name'] = str(ep_obj.airdate).split('-')[0]
elif ep_obj.show.is_anime:
current_params['name'] = '%s' % ep_obj.scene_absolute_number
else:
current_params['name'] = 'Season ' + str(ep_obj.scene_season)
current_params['name'] = 'Season %s' % (ep_obj.season, ep_obj.scene_season)[bool(ep_obj.show.is_scene)]
# search
if 1 == ep_obj.show.indexer:
@ -206,17 +206,19 @@ class BTNProvider(generic.TorrentProvider):
search_params = {'category': 'Episode'}
# episode
if ep_obj.show.air_by_date or ep_obj.show.sports:
if ep_obj.show.air_by_date or ep_obj.show.is_sports:
date_str = str(ep_obj.airdate)
# BTN uses dots in dates, we just search for the date since that
# combined with the series identifier should result in just one episode
search_params['name'] = date_str.replace('-', '.')
elif ep_obj.show.anime:
elif ep_obj.show.is_anime:
search_params['name'] = '%s' % ep_obj.scene_absolute_number
else:
# Do a general name search for the episode, formatted like SXXEYY
search_params['name'] = 'S%02dE%02d' % (ep_obj.scene_season, ep_obj.scene_episode)
season, episode = ((ep_obj.season, ep_obj.episode),
(ep_obj.scene_season, ep_obj.scene_episode))[bool(ep_obj.show.is_scene)]
search_params['name'] = 'S%02dE%02d' % (season, episode)
# search
if 1 == ep_obj.show.indexer:

View file

@ -149,7 +149,7 @@ class GenericProvider:
if GenericProvider.TORRENT == self.providerType:
try:
torrent_hash = re.findall('urn:btih:([0-9a-f]{32,40})', result.url)[0].upper()
torrent_hash = re.findall('(?i)urn:btih:([0-9a-f]{32,40})', result.url)[0].upper()
if 32 == len(torrent_hash):
torrent_hash = b16encode(b32decode(torrent_hash)).lower()
@ -158,34 +158,40 @@ class GenericProvider:
logger.log('Unable to extract torrent hash from link: ' + ex(result.url), logger.ERROR)
return False
urls = ['https://%s/%s.torrent' % (u, torrent_hash)
for u in ('torcache.net/torrent', 'torrage.com/torrent', 'getstrike.net/torrents/api/download')]
urls = ['http%s://%s/%s.torrent' % (u + (torrent_hash,))
for u in (('s', 'torcache.net/torrent'), ('s', 'getstrike.net/torrents/api/download'),
('', 'thetorrent.org'))]
except:
urls = [result.url]
filename = ek.ek(os.path.join, sickbeard.TORRENT_DIR,
helpers.sanitizeFileName(result.name) + '.' + self.providerType)
elif GenericProvider.NZB == self.providerType:
urls = [result.url]
filename = ek.ek(os.path.join, sickbeard.NZB_DIR,
helpers.sanitizeFileName(result.name) + '.' + self.providerType)
else:
return
for url in urls:
if helpers.download_file(url, filename, session=self.session):
logger.log(u'Downloading a result from ' + self.name + ' at ' + url)
cache_dir = sickbeard.CACHE_DIR or helpers._getTempDir()
base_name = '%s.%s' % (helpers.sanitizeFileName(result.name), self.providerType)
cache_file = ek.ek(os.path.join, cache_dir, base_name)
if GenericProvider.TORRENT == self.providerType:
logger.log(u'Saved magnet link to ' + filename, logger.MESSAGE)
else:
logger.log(u'Saved result to ' + filename, logger.MESSAGE)
if helpers.download_file(url, cache_file, session=self.session):
logger.log(u'Downloaded a result from %s at %s' % (self.name, url))
if self._verify_download(filename):
return True
elif ek.ek(os.path.isfile, filename):
ek.ek(os.remove, filename)
if self._verify_download(cache_file):
if GenericProvider.TORRENT == self.providerType:
final_dir, link_type = (sickbeard.TORRENT_DIR, 'magnet')
else:
final_dir, link_type = (sickbeard.NZB_DIR, 'nzb')
final_file = ek.ek(os.path.join, final_dir, base_name)
helpers.moveFile(cache_file, final_file)
if not ek.ek(os.path.isfile, cache_file) and ek.ek(os.path.isfile, final_file):
logger.log(u'Saved %s link to %s' % (link_type, final_file), logger.MESSAGE)
return True
if ek.ek(os.path.isfile, cache_file):
ek.ek(os.remove, cache_file)
logger.log(u'Failed to download result', logger.ERROR)
return False
@ -348,7 +354,7 @@ class GenericProvider:
version = parse_result.version
add_cache_entry = False
if not (show_obj.air_by_date or show_obj.sports):
if not (show_obj.air_by_date or show_obj.is_sports):
if 'sponly' == search_mode:
if len(parse_result.episode_numbers):
logger.log(u'This is supposed to be a season pack search but the result ' + title
@ -644,14 +650,14 @@ class TorrentProvider(GenericProvider):
def _get_season_search_strings(self, ep_obj, detail_only=False, scene=True):
if ep_obj.show.air_by_date or ep_obj.show.sports:
if ep_obj.show.air_by_date or ep_obj.show.is_sports:
ep_detail = str(ep_obj.airdate).split('-')[0]
elif ep_obj.show.anime:
elif ep_obj.show.is_anime:
ep_detail = ep_obj.scene_absolute_number
else:
ep_detail = 'S%02d' % int(ep_obj.scene_season)
ep_detail = 'S%02d' % int((ep_obj.season, ep_obj.scene_season)[bool(ep_obj.show.is_scene)])
detail = ({}, {'Season_only': [ep_detail]})[detail_only and not self.show.sports and not self.show.anime]
detail = ({}, {'Season_only': [ep_detail]})[detail_only and not self.show.is_sports and not self.show.is_anime]
return [dict({'Season': self._build_search_strings(ep_detail, scene)}.items() + detail.items())]
def _get_episode_search_strings(self, ep_obj, add_string='', detail_only=False, scene=True, sep_date=' ', use_or=True):
@ -659,18 +665,20 @@ class TorrentProvider(GenericProvider):
if not ep_obj:
return []
if self.show.air_by_date or self.show.sports:
if self.show.air_by_date or self.show.is_sports:
ep_detail = str(ep_obj.airdate).replace('-', sep_date)
if self.show.sports:
if self.show.is_sports:
month = ep_obj.airdate.strftime('%b')
ep_detail = ([ep_detail] + [month], '%s|%s' % (ep_detail, month))[use_or]
elif self.show.anime:
elif self.show.is_anime:
ep_detail = ep_obj.scene_absolute_number
else:
ep_detail = sickbeard.config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season,
'episodenumber': ep_obj.scene_episode}
append = (add_string, '')[self.show.anime]
detail = ({}, {'Episode_only': [ep_detail]})[detail_only and not self.show.sports and not self.show.anime]
season, episode = ((ep_obj.season, ep_obj.episode),
(ep_obj.scene_season, ep_obj.scene_episode))[bool(ep_obj.show.is_scene)]
ep_dict = {'seasonnumber': season, 'episodenumber': episode}
ep_detail = sickbeard.config.naming_ep_type[2] % ep_dict
append = (add_string, '')[self.show.is_anime]
detail = ({}, {'Episode_only': [ep_detail]})[detail_only and not self.show.is_sports and not self.show.is_anime]
return [dict({'Episode': self._build_search_strings(ep_detail, scene, append)}.items() + detail.items())]
def _build_search_strings(self, ep_detail, process_name=True, append=''):

View file

@ -117,18 +117,18 @@ class HDBitsProvider(generic.TorrentProvider):
if episode:
if show.air_by_date:
param['episode'] = str(episode.airdate).replace('-', '|')
elif show.sports:
elif show.is_sports:
param['episode'] = episode.airdate.strftime('%b')
elif show.anime:
elif show.is_anime:
param['episode'] = '%i' % int(episode.scene_absolute_number)
else:
param['season'] = episode.scene_season
param['episode'] = episode.scene_episode
if season:
if show.air_by_date or show.sports:
if show.air_by_date or show.is_sports:
param['season'] = str(season.airdate)[:7]
elif show.anime:
elif show.is_anime:
param['season'] = '%d' % season.scene_absolute_number
else:
param['season'] = season.scene_season

View file

@ -110,39 +110,39 @@ class KATProvider(generic.TorrentProvider):
def _get_season_search_strings(self, ep_obj, **kwargs):
if ep_obj.show.air_by_date or ep_obj.show.sports:
if ep_obj.show.air_by_date or ep_obj.show.is_sports:
airdate = str(ep_obj.airdate).split('-')[0]
ep_detail = [airdate, 'Season ' + airdate]
elif ep_obj.show.anime:
elif ep_obj.show.is_anime:
ep_detail = '%02i' % ep_obj.scene_absolute_number
else:
ep_detail = ['S%(s)02i -S%(s)02iE' % {'s': ep_obj.scene_season},
'Season %s -Ep*' % ep_obj.scene_season]
season = (ep_obj.season, ep_obj.scene_season)[bool(ep_obj.show.is_scene)]
ep_detail = ['S%(s)02i -S%(s)02iE' % {'s': season}, 'Season %s -Ep*' % season]
return [{'Season': self._build_search_strings(ep_detail, append=(' category:tv', '')[self.show.anime])}]
return [{'Season': self._build_search_strings(ep_detail, append=(' category:tv', '')[self.show.is_anime])}]
def _get_episode_search_strings(self, ep_obj, add_string='', **kwargs):
if not ep_obj:
return []
if self.show.air_by_date or self.show.sports:
if self.show.air_by_date or self.show.is_sports:
ep_detail = str(ep_obj.airdate).replace('-', ' ')
if self.show.sports:
if self.show.is_sports:
ep_detail += '|' + ep_obj.airdate.strftime('%b')
elif self.show.anime:
elif self.show.is_anime:
ep_detail = '%02i' % ep_obj.scene_absolute_number
else:
ep_detail = '%s|%s' % (config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season,
'episodenumber': ep_obj.scene_episode},
config.naming_ep_type[0] % {'seasonnumber': ep_obj.scene_season,
'episodenumber': ep_obj.scene_episode})
season, episode = ((ep_obj.season, ep_obj.episode),
(ep_obj.scene_season, ep_obj.scene_episode))[bool(ep_obj.show.is_scene)]
ep_dict = {'seasonnumber': season, 'episodenumber': episode}
ep_detail = '%s|%s' % (config.naming_ep_type[2] % ep_dict, config.naming_ep_type[0] % ep_dict)
# include provider specific appends
if not isinstance(add_string, list):
add_string = [add_string]
add_string = [x + ' category:tv' for x in add_string]
return [{'Episode': self._build_search_strings(ep_detail, append=(add_string, '')[self.show.anime])}]
return [{'Episode': self._build_search_strings(ep_detail, append=(add_string, '')[self.show.is_anime])}]
def _do_search(self, search_params, search_mode='eponly', epcount=0, age=0):

View file

@ -116,14 +116,14 @@ class NewznabProvider(generic.NZBProvider):
cur_params = {}
# season
if ep_obj.show.air_by_date or ep_obj.show.sports:
if ep_obj.show.air_by_date or ep_obj.show.is_sports:
date_str = str(ep_obj.airdate).split('-')[0]
cur_params['season'] = date_str
cur_params['q'] = date_str.replace('-', '.')
elif ep_obj.show.is_anime:
cur_params['season'] = '%d' % ep_obj.scene_absolute_number
else:
cur_params['season'] = str(ep_obj.scene_season)
cur_params['season'] = str((ep_obj.season, ep_obj.scene_season)[bool(ep_obj.show.is_scene)])
# search
rid = helpers.mapIndexersToShow(ep_obj.show)[2]
@ -151,15 +151,16 @@ class NewznabProvider(generic.NZBProvider):
if not ep_obj:
return [params]
if ep_obj.show.air_by_date or ep_obj.show.sports:
if ep_obj.show.air_by_date or ep_obj.show.is_sports:
date_str = str(ep_obj.airdate)
params['season'] = date_str.partition('-')[0]
params['ep'] = date_str.partition('-')[2].replace('-', '/')
elif ep_obj.show.anime:
elif ep_obj.show.is_anime:
params['ep'] = '%i' % int(
ep_obj.scene_absolute_number if int(ep_obj.scene_absolute_number) > 0 else ep_obj.scene_episode)
else:
params['season'], params['ep'] = ep_obj.scene_season, ep_obj.scene_episode
params['season'], params['ep'] = ((ep_obj.season, ep_obj.episode),
(ep_obj.scene_season, ep_obj.scene_episode))[bool(ep_obj.show.is_scene)]
# search
rid = helpers.mapIndexersToShow(ep_obj.show)[2]
@ -177,7 +178,7 @@ class NewznabProvider(generic.NZBProvider):
cur_return['q'] = cur_exception
to_return.append(cur_return)
if ep_obj.show.anime:
if ep_obj.show.is_anime:
# Experimental, add a searchstring without search explicitly for the episode!
# Remove the ?ep=e46 paramater and use add the episode number to the query paramater.
# Can be usefull for newznab indexers that do not have the episodes 100% parsed.

View file

@ -171,7 +171,7 @@ class RarbgProvider(generic.TorrentProvider):
def _get_episode_search_strings(self, ep_obj, add_string='', **kwargs):
search_params = generic.TorrentProvider._get_episode_search_strings(self, ep_obj, detail_only=True)
if self.show.air_by_date and self.show.sports:
if self.show.air_by_date and self.show.is_sports:
for x, types in enumerate(search_params):
for y, ep_type in enumerate(types):
search_params[x][ep_type][y] = '{{%s}}' % search_params[x][ep_type][y]

View file

@ -123,26 +123,26 @@ class ThePirateBayProvider(generic.TorrentProvider):
elif ep_obj.show.anime:
ep_detail = '%02i' % ep_obj.scene_absolute_number
else:
ep_detail = ['S%02d' % int(ep_obj.scene_season),
'Season %s -Ep*' % ep_obj.scene_season]
season = (ep_obj.season, ep_obj.scene_season)[bool(ep_obj.show.is_scene)]
ep_detail = ['S%02d' % int(season), 'Season %s -Ep*' % season]
return [{'Season': self._build_search_strings(ep_detail)}]
def _get_episode_search_strings(self, ep_obj, add_string='', **kwargs):
if self.show.air_by_date or self.show.sports:
if self.show.air_by_date or self.show.is_sports:
ep_detail = str(ep_obj.airdate).replace('-', ' ')
if self.show.sports:
if self.show.is_sports:
ep_detail += '|' + ep_obj.airdate.strftime('%b')
elif self.show.anime:
elif self.show.is_anime:
ep_detail = '%02i' % ep_obj.scene_absolute_number
else:
ep_detail = '%s|%s' % (config.naming_ep_type[2] % {'seasonnumber': ep_obj.scene_season,
'episodenumber': ep_obj.scene_episode},
config.naming_ep_type[0] % {'seasonnumber': ep_obj.scene_season,
'episodenumber': ep_obj.scene_episode})
season, episode = ((ep_obj.season, ep_obj.episode),
(ep_obj.scene_season, ep_obj.scene_episode))[bool(ep_obj.show.is_scene)]
ep_dict = {'seasonnumber': season, 'episodenumber': episode}
ep_detail = '%s|%s' % (config.naming_ep_type[2] % ep_dict, config.naming_ep_type[0] % ep_dict)
return [{'Episode': self._build_search_strings(ep_detail, append=(add_string, '')[self.show.anime])}]
return [{'Episode': self._build_search_strings(ep_detail, append=(add_string, '')[self.show.is_anime])}]
def _do_search(self, search_params, search_mode='eponly', epcount=0, age=0):

View file

@ -88,7 +88,8 @@ class ToTVProvider(generic.TorrentProvider):
def _get_season_search_strings(self, ep_obj, **kwargs):
return self._build_search_str(ep_obj, {'season': 'Season %02d' % ep_obj.scene_season})
return self._build_search_str(ep_obj, {'season': 'Season %02d' %
int((ep_obj.season, ep_obj.scene_season)[bool(ep_obj.show.is_scene)])})
def _get_episode_search_strings(self, ep_obj, add_string='', **kwargs):
@ -96,8 +97,9 @@ class ToTVProvider(generic.TorrentProvider):
return [{}]
# Do a general name search for the episode, formatted like SXXEYY
return self._build_search_str(ep_obj, {'episode': 'S%02dE%02d %s'
% (ep_obj.scene_season, ep_obj.scene_episode, add_string)})
season, episode = ((ep_obj.season, ep_obj.episode),
(ep_obj.scene_season, ep_obj.scene_episode))[bool(ep_obj.show.is_scene)]
return self._build_search_str(ep_obj, {'episode': 'S%02dE%02d %s' % (season, episode, add_string)})
@staticmethod
def _build_search_str(ep_obj, search_params):

View file

@ -471,15 +471,14 @@ class QueueItemAdd(ShowQueueItem):
# Load XEM data to DB for show
sickbeard.scene_numbering.xem_refresh(self.show.indexerid, self.show.indexer, force=True)
# check if show has XEM mapping and if user disabled scene numbering during add show, output availability to log
if not self.scene and self.show.indexerid in sickbeard.scene_exceptions.xem_tvdb_ids_list\
+ sickbeard.scene_exceptions.xem_rage_ids_list:
logger.log(u'Alternative scene episode numbers were disabled during add show. Edit show to enable them for searching.')
# update internal name cache
name_cache.buildNameCache(self.show)
# check if show has XEM mapping so we can determine if searches should go by scene numbering or indexer numbering.
if not self.scene and sickbeard.scene_numbering.get_xem_numbering_for_show(self.show.indexerid,
self.show.indexer):
self.show.scene = 1
self.finish()
def _finishEarly(self):

View file

@ -1091,7 +1091,7 @@ class Home(MainHandler):
t.submenu.append({'title': 'Update show in Kodi',
'path': 'home/updateKODI?showName=%s' % urllib.quote_plus(
showObj.name.encode('utf-8')), 'requires': self.haveKODI})
t.submenu.append({'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.indexerid})
t.submenu.append({'title': 'Media Renamer', 'path': 'home/testRename?show=%d' % showObj.indexerid})
if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled(
showObj) and showObj.subtitles:
t.submenu.append(
@ -1271,6 +1271,7 @@ class Home(MainHandler):
with showObj.lock:
t.show = showObj
t.scene_exceptions = get_scene_exceptions(showObj.indexerid)
t.show_has_scene_map = showObj.indexerid in sickbeard.scene_exceptions.xem_tvdb_ids_list + sickbeard.scene_exceptions.xem_rage_ids_list
return t.respond()
@ -2172,14 +2173,6 @@ class NewHomeAddShows(Home):
indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add)
if indexer_id and indexer and show_name:
use_provided_info = True
else:
use_provided_info = False
# tell the template whether we're giving it show name & Indexer ID
t.use_provided_info = use_provided_info
# use the given show_dir for the indexer search if available
if use_show_name:
t.default_show_name = show_name
@ -2196,7 +2189,9 @@ class NewHomeAddShows(Home):
elif type(other_shows) != list:
other_shows = [other_shows]
if use_provided_info:
# tell the template whether we're giving it show name & Indexer ID
t.use_provided_info = bool(indexer_id and indexer and show_name)
if t.use_provided_info:
t.provided_indexer_id = int(indexer_id or 0)
t.provided_indexer_name = show_name
@ -2208,6 +2203,8 @@ class NewHomeAddShows(Home):
t.blacklist = []
t.groups = []
t.show_scene_maps = sickbeard.scene_exceptions.xem_tvdb_ids_list + sickbeard.scene_exceptions.xem_rage_ids_list
return t.respond()
def recommendedShows(self, *args, **kwargs):