2014-03-10 05:18:05 +00:00
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
2014-05-23 12:37:22 +00:00
# This file is part of SickRage.
2014-03-10 05:18:05 +00:00
#
2014-05-23 12:37:22 +00:00
# SickRage is free software: you can redistribute it and/or modify
2014-03-10 05:18:05 +00:00
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
2014-05-23 12:37:22 +00:00
# SickRage is distributed in the hope that it will be useful,
2014-03-10 05:18:05 +00:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2014-05-26 06:29:22 +00:00
# GNU General Public License for more details.
2014-03-10 05:18:05 +00:00
#
# You should have received a copy of the GNU General Public License
2014-05-23 12:37:22 +00:00
# along with SickRage. If not, see <http://www.gnu.org/licenses/>.
2014-03-10 05:18:05 +00:00
import datetime
import os . path
import re
2014-05-07 07:50:49 +00:00
import threading
2014-03-10 05:18:05 +00:00
import regexes
2014-05-06 21:35:37 +00:00
import time
2014-03-10 05:18:05 +00:00
import sickbeard
2014-05-27 07:44:23 +00:00
from sickbeard import logger , helpers , scene_numbering , db
2014-05-26 06:29:22 +00:00
from sickbeard . exceptions import EpisodeNotFoundByAbsoluteNumberException
2014-04-30 22:07:18 +00:00
from dateutil import parser
2014-03-25 05:57:24 +00:00
2014-05-07 07:50:49 +00:00
nameparser_lock = threading . Lock ( )
2014-03-10 05:18:05 +00:00
class NameParser ( object ) :
2014-04-29 04:55:59 +00:00
ALL_REGEX = 0
NORMAL_REGEX = 1
SPORTS_REGEX = 2
2014-05-26 06:29:22 +00:00
ANIME_REGEX = 3
2014-04-28 09:15:29 +00:00
2014-05-26 06:29:22 +00:00
def __init__ ( self , file_name = True , show = None , useIndexers = False ) :
regexMode = self . ALL_REGEX
if show and show . is_anime :
regexMode = self . ANIME_REGEX
elif show and show . is_sports :
regexMode = self . SPORTS_REGEX
elif show and not show . is_anime and not show . is_sports :
regexMode = self . NORMAL_REGEX
2014-03-10 05:18:05 +00:00
self . file_name = file_name
2014-04-28 09:15:29 +00:00
self . regexMode = regexMode
2014-05-26 06:29:22 +00:00
self . compiled_regexes = { }
2014-04-30 22:07:18 +00:00
self . _compile_regexes ( self . regexMode )
2014-05-26 06:29:22 +00:00
self . showList = sickbeard . showList
self . useIndexers = useIndexers
2014-05-27 07:44:23 +00:00
self . show = show
2014-03-10 05:18:05 +00:00
def clean_series_name ( self , 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 " )
' an example 1.0 test '
>> > cleanRegexedSeriesName ( " an_example_1.0_test " )
' an example 1.0 test '
Stolen from dbr ' s tvnamer
"""
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
series_name = re . sub ( " ( \ D) \ .(?! \ s)( \ D) " , " \\ 1 \\ 2 " , series_name )
2014-03-25 05:57:24 +00:00
series_name = re . sub ( " ( \ d) \ .( \ d {4} ) " , " \\ 1 \\ 2 " , series_name ) # if it ends in a year then don't keep the dot
2014-03-10 05:18:05 +00:00
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 )
2014-03-25 05:57:24 +00:00
series_name = re . sub ( " ^ \ [.* \ ] " , " " , series_name )
2014-03-10 05:18:05 +00:00
return series_name . strip ( )
2014-04-28 09:15:29 +00:00
def _compile_regexes ( self , regexMode ) :
if regexMode < = self . ALL_REGEX :
2014-05-07 07:50:49 +00:00
logger . log ( u " Using ALL regexs " , logger . DEBUG )
2014-05-26 06:29:22 +00:00
uncompiled_regex = [ regexes . anime_regexes , regexes . sports_regexs , regexes . normal_regexes ]
2014-04-28 09:15:29 +00:00
elif regexMode == self . NORMAL_REGEX :
2014-05-07 07:50:49 +00:00
logger . log ( u " Using NORMAL regexs " , logger . DEBUG )
2014-05-26 06:29:22 +00:00
uncompiled_regex = [ regexes . normal_regexes ]
2014-04-28 09:15:29 +00:00
elif regexMode == self . SPORTS_REGEX :
2014-05-07 07:50:49 +00:00
logger . log ( u " Using SPORTS regexs " , logger . DEBUG )
2014-05-26 06:29:22 +00:00
uncompiled_regex = [ regexes . sports_regexs ]
elif regexMode == self . ANIME_REGEX :
logger . log ( u " Using ANIME regexs " , logger . DEBUG )
uncompiled_regex = [ regexes . anime_regexes , regexes . normal_regexes ]
2014-04-28 09:15:29 +00:00
else :
2014-05-07 07:50:49 +00:00
logger . log ( u " This is a programing ERROR. Fallback Using NORMAL regexs " , logger . ERROR )
2014-05-26 06:29:22 +00:00
uncompiled_regex = [ regexes . normal_regexes ]
2014-03-10 05:18:05 +00:00
2014-05-26 06:29:22 +00:00
for regexItem in uncompiled_regex :
for regex_type , regex in regexItem . items ( ) :
try :
self . compiled_regexes [ regex_type ]
except :
self . compiled_regexes [ regex_type ] = { }
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
for ( cur_pattern_name , cur_pattern ) in regex :
try :
cur_regex = re . compile ( cur_pattern , re . VERBOSE | re . IGNORECASE )
except re . error , errormsg :
logger . log ( u " WARNING: Invalid episode_pattern, %s . %s " % ( errormsg , cur_pattern ) )
else :
self . compiled_regexes [ regex_type ] . update ( { cur_pattern_name : cur_regex } )
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
def _parse_string ( self , name ) :
for cur_regex_type , cur_regexes in self . compiled_regexes . items ( ) if name else [ ] :
for cur_regex_name , cur_regex in cur_regexes . items ( ) :
match = cur_regex . match ( name )
2014-05-17 09:27:17 +00:00
2014-05-26 06:29:22 +00:00
if not match :
continue
2014-03-10 05:18:05 +00:00
2014-05-26 06:29:22 +00:00
result = ParseResult ( name )
result . which_regex = [ cur_regex_name ]
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
named_groups = match . groupdict ( ) . keys ( )
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
if ' series_name ' in named_groups :
result . series_name = match . group ( ' series_name ' )
if result . series_name :
result . series_name = self . clean_series_name ( result . series_name )
2014-03-10 05:18:05 +00:00
2014-05-26 06:29:22 +00:00
if ' season_num ' in named_groups :
tmp_season = int ( match . group ( ' season_num ' ) )
if cur_regex_name == ' bare ' and tmp_season in ( 19 , 20 ) :
continue
result . season_number = tmp_season
if ' ep_num ' in named_groups :
ep_num = self . _convert_number ( match . group ( ' ep_num ' ) )
if ' extra_ep_num ' in named_groups and match . group ( ' extra_ep_num ' ) :
result . episode_numbers = range ( ep_num , self . _convert_number ( match . group ( ' extra_ep_num ' ) ) + 1 )
else :
result . episode_numbers = [ ep_num ]
if ' ep_ab_num ' in named_groups :
ep_ab_num = self . _convert_number ( match . group ( ' ep_ab_num ' ) )
if ' extra_ab_ep_num ' in named_groups and match . group ( ' extra_ab_ep_num ' ) :
result . ab_episode_numbers = range ( ep_ab_num ,
self . _convert_number ( match . group ( ' extra_ab_ep_num ' ) ) + 1 )
else :
result . ab_episode_numbers = [ ep_ab_num ]
if ' sports_event_id ' in named_groups :
sports_event_id = match . group ( ' sports_event_id ' )
if sports_event_id :
result . sports_event_id = int ( match . group ( ' sports_event_id ' ) )
if ' sports_event_name ' in named_groups :
result . sports_event_name = match . group ( ' sports_event_name ' )
if result . sports_event_name :
result . sports_event_name = self . clean_series_name ( result . sports_event_name )
if ' sports_event_date ' in named_groups :
sports_event_date = match . group ( ' sports_event_date ' )
if sports_event_date :
try :
result . sports_event_date = parser . parse ( sports_event_date , fuzzy = True ) . date ( )
except :
continue
if ' air_year ' in named_groups and ' air_month ' in named_groups and ' air_day ' in named_groups :
year = int ( match . group ( ' air_year ' ) )
month = int ( match . group ( ' air_month ' ) )
day = int ( match . group ( ' air_day ' ) )
2014-04-30 12:10:13 +00:00
2014-04-30 22:07:18 +00:00
try :
2014-05-26 06:29:22 +00:00
dtStr = ' %s - %s - %s ' % ( year , month , day )
result . air_date = datetime . datetime . strptime ( dtStr , " % Y- % m- %d " ) . date ( )
2014-05-02 00:57:51 +00:00
except :
continue
2014-04-30 22:07:18 +00:00
2014-05-26 06:29:22 +00:00
if ' extra_info ' in named_groups :
tmp_extra_info = match . group ( ' extra_info ' )
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
# 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 (
r ' ([. _-]|^)(special|extra)s? \ w*([. _-]|$) ' , tmp_extra_info , re . I ) :
continue
result . extra_info = tmp_extra_info
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
if ' release_group ' in named_groups :
result . release_group = match . group ( ' release_group ' )
2014-03-10 05:18:05 +00:00
2014-05-27 07:44:23 +00:00
# determin show object for correct regex matching
if not self . show :
show = helpers . get_show_by_name ( result . series_name , useIndexer = self . useIndexers )
else :
show = self . show
2014-05-26 06:29:22 +00:00
if show and show . is_anime and cur_regex_type in [ ' anime ' , ' normal ' ] :
result . show = show
return result
elif show and show . is_sports and cur_regex_type == ' sports ' :
result . show = show
return result
elif cur_regex_type == ' normal ' :
2014-05-26 10:42:34 +00:00
result . show = show if show else None
2014-05-26 06:29:22 +00:00
return result
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
return None
def _combine_results ( self , first , second , attr ) :
# if the first doesn't exist then return the second or nothing
if not first :
if not second :
return None
else :
return getattr ( second , attr )
# if the second doesn't exist then return the first
if not second :
return getattr ( first , attr )
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
a = getattr ( first , attr )
b = getattr ( second , attr )
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
# if a is good use it
2014-03-20 18:03:22 +00:00
if a != None or ( type ( a ) == list and len ( a ) ) :
2014-03-10 05:18:05 +00:00
return a
# if not use b (if b isn't set it'll just be default)
else :
return b
2014-03-25 05:57:24 +00:00
def _unicodify ( self , obj , encoding = " utf-8 " ) :
2014-03-10 05:18:05 +00:00
if isinstance ( obj , basestring ) :
if not isinstance ( obj , unicode ) :
obj = unicode ( obj , encoding )
return obj
def _convert_number ( self , number ) :
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
try :
return int ( number )
except :
numeral_map = zip (
( 1000 , 900 , 500 , 400 , 100 , 90 , 50 , 40 , 10 , 9 , 5 , 4 , 1 ) ,
( ' M ' , ' CM ' , ' D ' , ' CD ' , ' C ' , ' XC ' , ' L ' , ' XL ' , ' X ' , ' IX ' , ' V ' , ' IV ' , ' I ' )
)
2014-03-25 05:57:24 +00:00
n = unicode ( number ) . upper ( )
2014-03-10 05:18:05 +00:00
i = result = 0
for integer , numeral in numeral_map :
while n [ i : i + len ( numeral ) ] == numeral :
result + = integer
i + = len ( numeral )
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
return result
2014-05-26 10:42:34 +00:00
def parse ( self , name , cache_result = True ) :
2014-03-10 05:18:05 +00:00
name = self . _unicodify ( name )
cached = name_parser_cache . get ( name )
if cached :
return cached
# break it into parts if there are any (dirname, file name, extension)
dir_name , file_name = os . path . split ( name )
ext_match = re . match ( ' (.*) \ . \ w { 3,4}$ ' , file_name )
if ext_match and self . file_name :
base_file_name = ext_match . group ( 1 )
else :
base_file_name = file_name
# use only the direct parent dir
dir_name = os . path . basename ( dir_name )
# set up a result to use
final_result = ParseResult ( name )
# try parsing the file name
file_name_result = self . _parse_string ( base_file_name )
# parse the dirname for extra info if needed
dir_name_result = self . _parse_string ( dir_name )
# build the ParseResult object
final_result . air_date = self . _combine_results ( file_name_result , dir_name_result , ' air_date ' )
2014-05-26 06:29:22 +00:00
final_result . ab_episode_numbers = self . _combine_results ( file_name_result , dir_name_result , ' ab_episode_numbers ' )
2014-04-30 12:10:13 +00:00
# sports event title
2014-05-02 03:03:44 +00:00
final_result . sports_event_id = self . _combine_results ( file_name_result , dir_name_result , ' sports_event_id ' )
final_result . sports_event_name = self . _combine_results ( file_name_result , dir_name_result , ' sports_event_name ' )
2014-04-30 22:07:18 +00:00
final_result . sports_event_date = self . _combine_results ( file_name_result , dir_name_result , ' sports_event_date ' )
2014-03-10 05:18:05 +00:00
if not final_result . air_date :
final_result . season_number = self . _combine_results ( file_name_result , dir_name_result , ' season_number ' )
final_result . episode_numbers = self . _combine_results ( file_name_result , dir_name_result , ' episode_numbers ' )
# if the dirname has a release group/show name I believe it over the filename
final_result . series_name = self . _combine_results ( dir_name_result , file_name_result , ' series_name ' )
2014-05-02 00:57:51 +00:00
2014-03-10 05:18:05 +00:00
final_result . extra_info = self . _combine_results ( dir_name_result , file_name_result , ' extra_info ' )
final_result . release_group = self . _combine_results ( dir_name_result , file_name_result , ' release_group ' )
final_result . which_regex = [ ]
if final_result == file_name_result :
final_result . which_regex = file_name_result . which_regex
elif final_result == dir_name_result :
final_result . which_regex = dir_name_result . which_regex
else :
if file_name_result :
final_result . which_regex + = file_name_result . which_regex
if dir_name_result :
final_result . which_regex + = dir_name_result . which_regex
2014-05-26 06:29:22 +00:00
final_result . show = self . _combine_results ( file_name_result , dir_name_result , ' show ' )
if final_result . show and final_result . show . is_anime and final_result . is_anime : # only need to to do another conversion if the scene2tvdb didn work
logger . log ( " Getting season and episodes from absolute numbers " , logger . DEBUG )
try :
_actual_season , _actual_episodes = helpers . get_all_episodes_from_absolute_number ( final_result . show ,
None ,
final_result . ab_episode_numbers )
except EpisodeNotFoundByAbsoluteNumberException :
logger . log ( str ( final_result . show . indexerid ) + " : Indexer object absolute number " + str (
final_result . ab_episode_numbers ) + " is incomplete, cant determin season and episode numbers " )
else :
final_result . season = _actual_season
final_result . episodes = _actual_episodes
2014-03-10 05:18:05 +00:00
# if there's no useful info in it then raise an exception
2014-03-20 18:03:22 +00:00
if final_result . season_number == None and not final_result . episode_numbers and final_result . air_date == None and not final_result . series_name :
2014-03-10 05:18:05 +00:00
raise InvalidNameException ( " Unable to parse " + name . encode ( sickbeard . SYS_ENCODING , ' xmlcharrefreplace ' ) )
2014-05-26 10:42:34 +00:00
if cache_result :
name_parser_cache . add ( name , final_result )
2014-03-10 05:18:05 +00:00
return final_result
2014-03-25 05:57:24 +00:00
2014-05-07 07:50:49 +00:00
2014-05-27 07:44:23 +00:00
def scene2indexer ( self , show , scene_name , season , episodes , absolute_numbers ) :
if not show : return self # need show object
# TODO: check if adb and make scene2indexer useable with correct numbers
out_season = None
out_episodes = [ ]
out_absolute_numbers = [ ]
# is the scene name a special season ?
# TODO: define if we get scene seasons or indexer seasons ... for now they are mostly the same ... and i will use them as scene seasons
_possible_seasons = sickbeard . scene_exceptions . get_scene_exception_by_name_multiple ( scene_name )
# filter possible_seasons
possible_seasons = [ ]
for cur_scene_indexer_id , cur_scene_season in _possible_seasons :
if cur_scene_indexer_id and str ( cur_scene_indexer_id ) != str ( show . indexerid ) :
logger . log ( " Indexer ID mismatch: " + str ( show . indexerid ) + " now: " + str ( cur_scene_indexer_id ) ,
logger . ERROR )
raise MultipleSceneShowResults ( " indexerid mismatch " )
# don't add season -1 since this is a generic name and not a real season... or if we get None
# if this was the only result possible_seasons will stay empty and the next parts will look in the general matter
if cur_scene_season == - 1 or cur_scene_season == None :
continue
possible_seasons . append ( cur_scene_season )
# if not possible_seasons: # no special season name was used or we could not find it
logger . log (
" possible seasons for ' " + scene_name + " ' ( " + str ( show . indexerid ) + " ) are " + str ( possible_seasons ) ,
logger . DEBUG )
# lets just get a db connection we will need it anyway
cacheDB = db . DBConnection ( ' cache.db ' )
# should we use absolute_numbers -> anime or season, episodes -> normal show
if show . is_anime :
logger . log (
u " ' " + show . name + " ' is an anime i will scene convert the absolute numbers " + str ( absolute_numbers ) ,
logger . DEBUG )
if possible_seasons :
# check if we have a scene_absolute_number in the possible seasons
for cur_possible_season in possible_seasons :
# and for all absolute numbers
for cur_ab_number in absolute_numbers :
namesSQlResult = cacheDB . select (
" SELECT season, episode, absolute_number FROM xem_numbering WHERE indexer_id = ? and scene_season = ? and scene_absolute_number = ? " ,
[ show . indexerid , cur_possible_season , cur_ab_number ] )
if len ( namesSQlResult ) > 1 :
logger . log (
" Multiple episodes for a absolute number and season. check XEM numbering " ,
logger . ERROR )
raise MultipleSceneEpisodeResults ( " Multiple episodes for a absolute number and season " )
elif len ( namesSQlResult ) == 0 :
break # break out of current absolute_numbers -> next season ... this is not a good sign
# if we are here we found ONE episode for this season absolute number
# logger.log(u"I found matching episode: " + namesSQlResult[0]['name'], logger.DEBUG)
out_episodes . append ( int ( namesSQlResult [ 0 ] [ ' episode ' ] ) )
out_absolute_numbers . append ( int ( namesSQlResult [ 0 ] [ ' absolute_number ' ] ) )
out_season = int ( namesSQlResult [ 0 ] [
' season ' ] ) # note this will always use the last season we got ... this will be a problem on double episodes that break the season barrier
if out_season : # if we found a episode in the cur_possible_season we dont need / want to look at the other season possibilities
break
else : # no possible seasons from the scene names lets look at this more generic
for cur_ab_number in absolute_numbers :
namesSQlResult = cacheDB . select (
" SELECT season, episode, absolute_number FROM xem_numbering WHERE indexer_id = ? and scene_absolute_number = ? " ,
[ show . indexerid , cur_ab_number ] )
if len ( namesSQlResult ) > 1 :
logger . log (
" Multiple episodes for a absolute number. this might happend because we are missing a scene name for this season. xem lacking behind ? " ,
logger . ERROR )
raise MultipleSceneEpisodeResults ( " Multiple episodes for a absolute number " )
elif len ( namesSQlResult ) == 0 :
continue
# if we are here we found ONE episode for this season absolute number
# logger.log(u"I found matching episode: " + namesSQlResult[0]['name'], logger.DEBUG)
out_episodes . append ( int ( namesSQlResult [ 0 ] [ ' episode ' ] ) )
out_absolute_numbers . append ( int ( namesSQlResult [ 0 ] [ ' absolute_number ' ] ) )
out_season = int ( namesSQlResult [ 0 ] [
' season ' ] ) # note this will always use the last season we got ... this will be a problem on double episodes that break the season barrier
if not out_season : # we did not find anything in the loops ? damit there is no episode
logger . log ( " No episode found for these scene numbers. asuming indexer numbers " , logger . DEBUG )
# we still have to convert the absolute number to sxxexx ... but that is done not here
else :
logger . log ( u " ' " + show . name + " ' is a normal show i will scene convert the season and episodes " + str (
season ) + " x " + str ( episodes ) , logger . DEBUG )
out_absolute_numbers = None
if possible_seasons :
# check if we have a scene_absolute_number in the possible seasons
for cur_possible_season in possible_seasons :
# and for all episode
for cur_episode in episodes :
namesSQlResult = cacheDB . select (
" SELECT season, episode FROM xem_numbering WHERE indexer_id = ? and scene_season = ? and scene_episode = ? " ,
[ show . indexerid , cur_possible_season , cur_episode ] )
if len ( namesSQlResult ) > 1 :
logger . log (
" Multiple episodes for season episode number combination. this should not be check xem configuration " ,
logger . ERROR )
raise MultipleSceneEpisodeResults ( " Multiple episodes for season episode number combination " )
elif len ( namesSQlResult ) == 0 :
break # break out of current episode -> next season ... this is not a good sign
# if we are here we found ONE episode for this season absolute number
# logger.log(u"I found matching episode: " + namesSQlResult[0]['name'], logger.DEBUG)
out_episodes . append ( int ( namesSQlResult [ 0 ] [ ' episode ' ] ) )
out_season = int ( namesSQlResult [ 0 ] [
' season ' ] ) # note this will always use the last season we got ... this will be a problem on double episodes that break the season barrier
if out_season : # if we found a episode in the cur_possible_season we dont need / want to look at the other posibilites
break
else : # no possible seasons from the scene names lets look at this more generic
for cur_episode in episodes :
namesSQlResult = cacheDB . select (
" SELECT season, episode FROM xem_numbering WHERE indexer_id = ? and scene_episode = ? and scene_season = ? " ,
[ show . indexerid , cur_episode , season ] )
if len ( namesSQlResult ) > 1 :
logger . log (
" Multiple episodes for season episode number combination. this might happend because we are missing a scene name for this season. xem lacking behind ? " ,
logger . ERROR )
raise MultipleSceneEpisodeResults ( " Multiple episodes for season episode number combination " )
elif len ( namesSQlResult ) == 0 :
continue
# if we are here we found ONE episode for this season absolute number
# logger.log(u"I found matching episode: " + namesSQlResult[0]['name'], logger.DEBUG)
out_episodes . append ( int ( namesSQlResult [ 0 ] [ ' episode ' ] ) )
out_season = int ( namesSQlResult [ 0 ] [
' season ' ] ) # note this will always use the last season we got ... this will be a problem on double episodes that break the season barrier
# this is only done for normal shows
if not out_season : # we did not find anything in the loops ? darn there is no episode
logger . log ( " No episode found for these scene numbers. assuming these are valid indexer numbers " ,
logger . DEBUG )
out_season = season
out_episodes = episodes
out_absolute_numbers = absolute_numbers
# okay that was easy we found the correct season and episode numbers
return ( out_season , out_episodes , out_absolute_numbers )
2014-03-10 05:18:05 +00:00
class ParseResult ( object ) :
def __init__ ( self ,
original_name ,
series_name = None ,
2014-05-02 03:03:44 +00:00
sports_event_id = None ,
sports_event_name = None ,
2014-04-30 22:07:18 +00:00
sports_event_date = None ,
2014-03-10 05:18:05 +00:00
season_number = None ,
episode_numbers = None ,
extra_info = None ,
release_group = None ,
2014-04-28 09:15:29 +00:00
air_date = None ,
2014-05-26 06:29:22 +00:00
ab_episode_numbers = None ,
show = None
2014-03-25 05:57:24 +00:00
) :
2014-03-10 05:18:05 +00:00
self . original_name = original_name
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
self . series_name = series_name
self . season_number = season_number
if not episode_numbers :
self . episode_numbers = [ ]
else :
self . episode_numbers = episode_numbers
2014-05-26 06:29:22 +00:00
if not ab_episode_numbers :
self . ab_episode_numbers = [ ]
else :
self . ab_episode_numbers = ab_episode_numbers
2014-03-10 05:18:05 +00:00
self . extra_info = extra_info
self . release_group = release_group
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
self . air_date = air_date
2014-04-30 12:10:13 +00:00
2014-05-02 03:03:44 +00:00
self . sports_event_id = sports_event_id
self . sports_event_name = sports_event_name
2014-04-30 22:07:18 +00:00
self . sports_event_date = sports_event_date
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
self . which_regex = None
2014-05-26 06:29:22 +00:00
self . show = show
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
def __eq__ ( self , other ) :
if not other :
return False
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
if self . series_name != other . series_name :
return False
if self . season_number != other . season_number :
return False
if self . episode_numbers != other . episode_numbers :
return False
if self . extra_info != other . extra_info :
return False
if self . release_group != other . release_group :
return False
if self . air_date != other . air_date :
return False
2014-05-02 03:03:44 +00:00
if self . sports_event_id != other . sports_event_id :
return False
if self . sports_event_name != other . sports_event_name :
return False
2014-04-30 22:07:18 +00:00
if self . sports_event_date != other . sports_event_date :
return False
2014-05-26 06:29:22 +00:00
if self . ab_episode_numbers != other . ab_episode_numbers :
return False
if self . show != other . show :
return False
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
return True
def __str__ ( self ) :
2014-03-20 18:03:22 +00:00
if self . series_name != None :
2014-03-10 05:18:05 +00:00
to_return = self . series_name + u ' - '
else :
to_return = u ' '
2014-03-20 18:03:22 +00:00
if self . season_number != None :
2014-03-25 05:57:24 +00:00
to_return + = ' S ' + str ( self . season_number )
2014-03-10 05:18:05 +00:00
if self . episode_numbers and len ( self . episode_numbers ) :
for e in self . episode_numbers :
2014-03-25 05:57:24 +00:00
to_return + = ' E ' + str ( e )
2014-03-10 05:18:05 +00:00
if self . air_by_date :
to_return + = str ( self . air_date )
2014-04-28 09:15:29 +00:00
if self . sports :
2014-05-02 03:03:44 +00:00
to_return + = str ( self . sports_event_name )
to_return + = str ( self . sports_event_id )
2014-04-30 22:07:18 +00:00
to_return + = str ( self . sports_event_date )
2014-05-26 06:29:22 +00:00
if self . ab_episode_numbers :
to_return + = ' absolute_numbers: ' + str ( self . ab_episode_numbers )
2014-03-10 05:18:05 +00:00
if self . extra_info :
to_return + = ' - ' + self . extra_info
if self . release_group :
to_return + = ' ( ' + self . release_group + ' ) '
2014-03-25 05:57:24 +00:00
to_return + = ' [ABD: ' + str ( self . air_by_date ) + ' ] '
2014-04-28 09:15:29 +00:00
to_return + = ' [SPORTS: ' + str ( self . sports ) + ' ] '
2014-05-26 06:29:22 +00:00
to_return + = ' [ANIME: ' + str ( self . is_anime ) + ' ] '
2014-04-28 09:15:29 +00:00
to_return + = ' [whichReg: ' + str ( self . which_regex ) + ' ] '
2014-03-10 05:18:05 +00:00
return to_return . encode ( ' utf-8 ' )
2014-05-26 10:42:34 +00:00
def convert ( self ) :
if not self . show : return self # need show object
2014-05-19 13:08:16 +00:00
if not self . season_number : return self # can't work without a season
if not len ( self . episode_numbers ) : return self # need at least one episode
2014-05-14 08:01:36 +00:00
if self . air_by_date or self . sports : return self # scene numbering does not apply to air-by-date
2014-05-03 09:23:26 +00:00
2014-05-01 11:36:16 +00:00
new_episode_numbers = [ ]
new_season_numbers = [ ]
2014-05-26 06:29:22 +00:00
new_absolute_numbers = [ ]
for i , epNo in enumerate ( self . episode_numbers ) :
abNo = None
if len ( self . ab_episode_numbers ) :
abNo = self . ab_episode_numbers [ i ]
2014-05-27 07:44:23 +00:00
( s , e , a ) = scene_numbering . get_indexer_numbering ( self . show . indexerid , self . show . indexer ,
self . season_number ,
2014-05-26 06:29:22 +00:00
epNo , abNo )
2014-05-01 11:36:16 +00:00
new_episode_numbers . append ( e )
new_season_numbers . append ( s )
2014-05-26 06:29:22 +00:00
new_absolute_numbers . append ( a )
2014-05-01 11:36:16 +00:00
# need to do a quick sanity check here. It's possible that we now have episodes
# 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 "
2014-05-23 12:37:22 +00:00
" sickrage does not support this. "
2014-05-01 11:36:16 +00:00
" Sorry. " % ( str ( new_season_numbers ) ) )
# I guess it's possible that we'd have duplicate episodes too, so lets
# eliminate them
new_episode_numbers = list ( set ( new_episode_numbers ) )
new_episode_numbers . sort ( )
2014-05-26 06:29:22 +00:00
# dedupe absolute numbers
new_absolute_numbers = list ( set ( new_absolute_numbers ) )
new_absolute_numbers . sort ( )
self . ab_episode_numbers = new_absolute_numbers
2014-05-01 11:36:16 +00:00
self . episode_numbers = new_episode_numbers
self . season_number = new_season_numbers [ 0 ]
2014-04-30 12:10:13 +00:00
2014-04-30 12:21:16 +00:00
return self
2014-04-30 12:18:20 +00:00
2014-03-10 05:18:05 +00:00
def _is_air_by_date ( self ) :
2014-04-30 12:10:13 +00:00
if self . season_number == None and len ( self . episode_numbers ) == 0 and self . air_date :
2014-03-10 05:18:05 +00:00
return True
return False
2014-05-07 07:50:49 +00:00
2014-03-10 05:18:05 +00:00
air_by_date = property ( _is_air_by_date )
2014-03-25 05:57:24 +00:00
2014-05-26 06:29:22 +00:00
def _is_anime ( self ) :
if self . ab_episode_numbers :
2014-05-26 10:42:34 +00:00
if self . show and self . show . is_anime :
return True
2014-05-26 06:29:22 +00:00
return False
is_anime = property ( _is_anime )
2014-04-28 09:15:29 +00:00
def _is_sports ( self ) :
2014-05-02 00:57:51 +00:00
if self . sports_event_date :
2014-04-28 09:15:29 +00:00
return True
return False
2014-05-07 07:50:49 +00:00
2014-04-28 09:15:29 +00:00
sports = property ( _is_sports )
2014-05-07 07:50:49 +00:00
2014-03-10 05:18:05 +00:00
class NameParserCache ( object ) :
_previous_parsed = { }
_cache_size = 100
2014-03-25 05:57:24 +00:00
2014-05-01 22:53:37 +00:00
def add ( self , name , parse_result ) :
2014-05-11 14:17:11 +00:00
self . _previous_parsed [ name ] = parse_result
_current_cache_size = len ( self . _previous_parsed )
if _current_cache_size > self . _cache_size :
for i in range ( _current_cache_size - self . _cache_size ) :
del self . _previous_parsed [ self . _previous_parsed . keys ( ) [ 0 ] ]
2014-03-25 05:57:24 +00:00
2014-05-01 22:53:37 +00:00
def get ( self , name ) :
2014-05-11 14:17:11 +00:00
if name in self . _previous_parsed :
logger . log ( " Using cached parse result for: " + name , logger . DEBUG )
return self . _previous_parsed [ name ]
2014-03-10 05:18:05 +00:00
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
name_parser_cache = NameParserCache ( )
2014-03-25 05:57:24 +00:00
2014-03-10 05:18:05 +00:00
class InvalidNameException ( Exception ) :
" The given name is not valid "
2014-05-27 07:44:23 +00:00
class MultipleSceneShowResults ( Exception ) :
pass
class MultipleSceneEpisodeResults ( Exception ) :
pass