@ -2,6 +2,8 @@ import glob, logging, os, re
from datetime import datetime , timedelta
from datetime import datetime , timedelta
from modules import util
from modules import util
from modules . util import Failed
from modules . util import Failed
from plexapi . collection import Collections
from plexapi . exceptions import BadRequest , NotFound
logger = logging . getLogger ( " Plex Meta Manager " )
logger = logging . getLogger ( " Plex Meta Manager " )
@ -17,11 +19,17 @@ class CollectionBuilder:
" show_missing " : library . show_missing ,
" show_missing " : library . show_missing ,
" save_missing " : library . save_missing
" save_missing " : library . save_missing
}
}
self . missing_movies = [ ]
self . missing_shows = [ ]
self . methods = [ ]
self . methods = [ ]
self . filters = [ ]
self . filters = [ ]
self . posters = [ ]
self . posters = { }
self . backgrounds = [ ]
self . backgrounds = { }
self . schedule = None
self . summaries = { }
self . schedule = " "
self . rating_key_map = { }
current_time = datetime . now ( )
current_year = current_time . year
if " template " in data :
if " template " in data :
if not self . library . templates :
if not self . library . templates :
@ -37,16 +45,17 @@ class CollectionBuilder:
elif not data_template [ " name " ] :
elif not data_template [ " name " ] :
raise Failed ( " Collection Error: template sub-attribute name is blank " )
raise Failed ( " Collection Error: template sub-attribute name is blank " )
elif data_template [ " name " ] not in self . library . templates :
elif data_template [ " name " ] not in self . library . templates :
raise Failed ( " Collection Error: template { } not found " . format ( data_template [ " name " ] ) )
raise Failed ( f " Collection Error: template { data_template [ ' name ' ] } not found " )
elif not isinstance ( self . library . templates [ data_template [ " name " ] ] , dict ) :
elif not isinstance ( self . library . templates [ data_template [ " name " ] ] , dict ) :
raise Failed ( " Collection Error: template { } is not a dictionary " . format ( data_template [ " name " ] ) )
raise Failed ( f " Collection Error: template { data_template [ ' name ' ] } is not a dictionary " )
else :
else :
for tm in data_template :
for tm in data_template :
if not data_template [ tm ] :
if not data_template [ tm ] :
raise Failed ( " Collection Error: template sub-attribute { } is blank " . format ( data_template [ tm ] ) )
raise Failed ( f " Collection Error: template sub-attribute { data_template [ tm ] } is blank " )
template_name = data_template [ " name " ]
template_name = data_template [ " name " ]
template = self . library . templates [ template_name ]
template = self . library . templates [ template_name ]
default = { }
default = { }
if " default " in template :
if " default " in template :
if template [ " default " ] :
if template [ " default " ] :
@ -55,33 +64,48 @@ class CollectionBuilder:
if template [ " default " ] [ dv ] :
if template [ " default " ] [ dv ] :
default [ dv ] = template [ " default " ] [ dv ]
default [ dv ] = template [ " default " ] [ dv ]
else :
else :
raise Failed ( " Collection Error: template default sub-attribute { } is blank " . format ( dv ) )
raise Failed ( f " Collection Error: template default sub-attribute { dv } is blank " )
else :
else :
raise Failed ( " Collection Error: template sub-attribute default is not a dictionary " )
raise Failed ( " Collection Error: template sub-attribute default is not a dictionary " )
else :
else :
raise Failed ( " Collection Error: template sub-attribute default is blank " )
raise Failed ( " Collection Error: template sub-attribute default is blank " )
optional = [ ]
if " optional " in template :
if template [ " optional " ] :
if isinstance ( template [ " optional " ] , list ) :
for op in template [ " optional " ] :
if op not in default :
optional . append ( op )
else :
logger . warning ( f " Template Warning: variable { op } cannot be optional if it has a default " )
else :
optional . append ( str ( template [ " optional " ] ) )
else :
raise Failed ( " Collection Error: template sub-attribute optional is blank " )
for m in template :
for m in template :
if m not in self . data and m != " default " :
if m not in self . data and m not in [ " default " , " optional " ] :
if template [ m ] :
if template [ m ] :
attr = None
def replace_txt ( txt ) :
def replace_txt ( txt ) :
txt = str ( txt )
txt = str ( txt )
for tm in data_template :
for option in optional :
if tm != " name " and " << {} >> " . format ( tm ) in txt :
if option not in data_template and f " << { option } >> " in txt :
txt = txt . replace ( " << {} >> " . format ( tm ) , str ( data_template [ tm ] ) )
raise Failed ( " remove attribute " )
for template_method in data_template :
if template_method != " name " and f " << { template_method } >> " in txt :
txt = txt . replace ( f " << { template_method } >> " , str ( data_template [ template_method ] ) )
if " <<collection_name>> " in txt :
if " <<collection_name>> " in txt :
txt = txt . replace ( " <<collection_name>> " , str ( self . name ) )
txt = txt . replace ( " <<collection_name>> " , str ( self . name ) )
for dm in default :
for dm in default :
if " << { }>> " . format ( dm ) in txt :
if f " << { dm }>> " in txt :
txt = txt . replace ( " << { }>> " . format ( dm ) , str ( default [ dm ] ) )
txt = txt . replace ( f " << { dm }>> " , str ( default [ dm ] ) )
if txt in [ " true " , " True " ] : return True
if txt in [ " true " , " True " ] : return True
elif txt in [ " false " , " False " ] : return False
elif txt in [ " false " , " False " ] : return False
else :
else :
try : return int ( txt )
try : return int ( txt )
except ValueError : return txt
except ValueError : return txt
try :
if isinstance ( template [ m ] , dict ) :
if isinstance ( template [ m ] , dict ) :
attr = { }
attr = { }
for sm in template [ m ] :
for sm in template [ m ] :
@ -104,9 +128,11 @@ class CollectionBuilder:
attr . append ( replace_txt ( li ) )
attr . append ( replace_txt ( li ) )
else :
else :
attr = replace_txt ( template [ m ] )
attr = replace_txt ( template [ m ] )
except Failed :
continue
self . data [ m ] = attr
self . data [ m ] = attr
else :
else :
raise Failed ( " Collection Error: template attribute { } is blank " . format ( m ) )
raise Failed ( f " Collection Error: template attribute { m } is blank " )
skip_collection = True
skip_collection = True
if " schedule " not in data :
if " schedule " not in data :
@ -116,7 +142,6 @@ class CollectionBuilder:
skip_collection = False
skip_collection = False
else :
else :
schedule_list = util . get_list ( data [ " schedule " ] )
schedule_list = util . get_list ( data [ " schedule " ] )
current_time = datetime . now ( )
next_month = current_time . replace ( day = 28 ) + timedelta ( days = 4 )
next_month = current_time . replace ( day = 28 ) + timedelta ( days = 4 )
last_day = next_month - timedelta ( days = next_month . day )
last_day = next_month - timedelta ( days = next_month . day )
for schedule in schedule_list :
for schedule in schedule_list :
@ -130,49 +155,44 @@ class CollectionBuilder:
if run_time . startswith ( " week " ) :
if run_time . startswith ( " week " ) :
if param . lower ( ) in util . days_alias :
if param . lower ( ) in util . days_alias :
weekday = util . days_alias [ param . lower ( ) ]
weekday = util . days_alias [ param . lower ( ) ]
self . schedule + = " \n Scheduled weekly on { }" . format ( util . pretty_days [ weekday ] )
self . schedule + = f " \n Scheduled weekly on { util . pretty_days [ weekday ] } "
if weekday == current_time . weekday ( ) :
if weekday == current_time . weekday ( ) :
skip_collection = False
skip_collection = False
else :
else :
logger . error ( " Collection Error: weekly schedule attribute { } invalid must be a day of the wee e k i.e. weekly(Monday)" . format ( schedule ) )
logger . error ( f " Collection Error: weekly schedule attribute { schedule } invalid must be a day of the wee k i.e. weekly(Monday)" )
elif run_time . startswith ( " month " ) :
elif run_time . startswith ( " month " ) :
try :
try :
if 1 < = int ( param ) < = 31 :
if 1 < = int ( param ) < = 31 :
self . schedule + = " \n Scheduled monthly on the { }" . format ( util . make_ordinal ( param ) )
self . schedule + = f " \n Scheduled monthly on the { util . make_ordinal ( param ) } "
if current_time . day == int ( param ) or ( current_time . day == last_day . day and int ( param ) > last_day . day ) :
if current_time . day == int ( param ) or ( current_time . day == last_day . day and int ( param ) > last_day . day ) :
skip_collection = False
skip_collection = False
else :
else :
logger . error ( " Collection Error: monthly schedule attribute { } invalid must be between 1 and 31 " . format ( schedule ) )
logger . error ( f " Collection Error: monthly schedule attribute { schedule } invalid must be between 1 and 31 " )
except ValueError :
except ValueError :
logger . error ( " Collection Error: monthly schedule attribute { } invalid must be an integer " . format ( schedule ) )
logger . error ( f " Collection Error: monthly schedule attribute { schedule } invalid must be an integer " )
elif run_time . startswith ( " year " ) :
elif run_time . startswith ( " year " ) :
match = re . match ( " ^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$ " , param )
match = re . match ( " ^(1[0-2]|0?[1-9])/(3[01]|[12][0-9]|0?[1-9])$ " , param )
if match :
if match :
month = int ( match . group ( 1 ) )
month = int ( match . group ( 1 ) )
day = int ( match . group ( 2 ) )
day = int ( match . group ( 2 ) )
self . schedule + = " \n Scheduled yearly on { } {} " . format ( util . pretty_months [ month ] , util . make_ordinal ( day ) )
self . schedule + = f " \n Scheduled yearly on { util . pretty_months [ month ] } { util . make_ordinal ( day ) } "
if current_time . month == month and ( current_time . day == day or ( current_time . day == last_day . day and day > last_day . day ) ) :
if current_time . month == month and ( current_time . day == day or ( current_time . day == last_day . day and day > last_day . day ) ) :
skip_collection = False
skip_collection = False
else :
else :
logger . error ( " Collection Error: yearly schedule attribute { } invalid must be in the MM/DD format i.e. yearly(11/22) " . format ( schedule ) )
logger . error ( f " Collection Error: yearly schedule attribute { schedule } invalid must be in the MM/DD format i.e. yearly(11/22) " )
else :
else :
logger . error ( " Collection Error: failed to parse schedule: { }" . format ( schedule ) )
logger . error ( f " Collection Error: failed to parse schedule: { schedule } " )
else :
else :
logger . error ( " Collection Error: schedule attribute { } invalid " . format ( schedule ) )
logger . error ( f " Collection Error: schedule attribute { schedule } invalid " )
if self . schedule is None :
if len ( self . schedule ) == 0 :
skip_collection = False
skip_collection = False
if skip_collection :
if skip_collection :
raise Failed ( " Skipping Collection {} " . format ( c ) )
raise Failed ( f" { self . schedule } \n \n Collection { self . name } not scheduled to run " )
logger . info ( " Scanning { } Collection " . format ( self . name ) )
logger . info ( f " Scanning { self . name } Collection " )
self . collectionless = " plex_collectionless " in data
self . collectionless = " plex_collectionless " in data
self . run_again = " run_again " in data
self . sync = self . library . sync_mode == " sync "
if " sync_mode " in data :
if not data [ " sync_mode " ] : logger . warning ( " Collection Warning: sync_mode attribute is blank using general: {} " . format ( self . library . sync_mode ) )
elif data [ " sync_mode " ] not in [ " append " , " sync " ] : logger . warning ( " Collection Warning: {} sync_mode invalid using general: {} " . format ( self . library . sync_mode , data [ " sync_mode " ] ) )
else : self . sync = data [ " sync_mode " ] == " sync "
if " tmdb_person " in data :
if " tmdb_person " in data :
if data [ " tmdb_person " ] :
if data [ " tmdb_person " ] :
@ -180,80 +200,85 @@ class CollectionBuilder:
for tmdb_id in util . get_int_list ( data [ " tmdb_person " ] , " TMDb Person ID " ) :
for tmdb_id in util . get_int_list ( data [ " tmdb_person " ] , " TMDb Person ID " ) :
person = config . TMDb . get_person ( tmdb_id )
person = config . TMDb . get_person ( tmdb_id )
valid_names . append ( person . name )
valid_names . append ( person . name )
if " summary " not in self . details and hasattr ( person , " biography " ) and person . biography :
if hasattr ( person , " biography " ) and person . biography :
self . details[ " summary " ] = person . biography
self . summaries[ " tmdb_person " ] = person . biography
if " poster " not in self . details and hasattr ( person , " profile_path " ) and person . profile_path :
if hasattr ( person , " profile_path " ) and person . profile_path :
self . details[ " poster " ] = ( " url " , " {} {} " . format ( config . TMDb . image_url , person . profile_path ) , " tmdb_person " )
self . posters[ " tmdb_person " ] = f " { config . TMDb . image_url } { person . profile_path } "
if len ( valid_names ) > 0 : self . details [ " tmdb_person " ] = valid_names
if len ( valid_names ) > 0 : self . details [ " tmdb_person " ] = valid_names
else : raise Failed ( " Collection Error: No valid TMDb Person IDs in { }" . format ( data [ " tmdb_person " ] ) )
else : raise Failed ( f " Collection Error: No valid TMDb Person IDs in { data [ ' tmdb_person ' ] } " )
else :
else :
raise Failed ( " Collection Error: tmdb_person attribute is blank " )
raise Failed ( " Collection Error: tmdb_person attribute is blank " )
for m in data :
for m in data :
if " tmdb " in m and not config . TMDb : raise Failed ( " Collection Error: { } requires TMDb to be configured " . format ( m ) )
if " tmdb " in m and not config . TMDb : raise Failed ( f " Collection Error: { m } requires TMDb to be configured " )
elif " trakt " in m and not config . Trakt : raise Failed ( " Collection Error: { } requires Trakt todo be configured " . format ( m ) )
elif " trakt " in m and not config . Trakt : raise Failed ( f " Collection Error: { m } requires Trakt todo be configured " )
elif " imdb " in m and not config . IMDb : raise Failed ( " Collection Error: { } requires TMDb or Trakt to be configured " . format ( m ) )
elif " imdb " in m and not config . IMDb : raise Failed ( f " Collection Error: { m } requires TMDb or Trakt to be configured " )
elif " tautulli " in m and not self . library . Tautulli : raise Failed ( " Collection Error: { } requires Tautulli to be configured " . format ( m ) )
elif " tautulli " in m and not self . library . Tautulli : raise Failed ( f " Collection Error: { m } requires Tautulli to be configured " )
elif " mal " in m and not config . MyAnimeList : raise Failed ( " Collection Error: { } requires MyAnimeList to be configured " . format ( m ) )
elif " mal " in m and not config . MyAnimeList : raise Failed ( f " Collection Error: { m } requires MyAnimeList to be configured " )
elif data [ m ] is not None :
elif data [ m ] is not None :
logger . debug ( " " )
logger . debug ( " " )
logger . debug ( " Method: {} " . format ( m ) )
logger . debug ( f" Method: { m } " )
logger . debug ( " Value: {} " . format ( data [ m ] ) )
logger . debug ( f" Value: { data [ m ] } " )
if m in util . method_alias :
if m in util . method_alias :
method_name = util . method_alias [ m ]
method_name = util . method_alias [ m ]
logger . warning ( " Collection Warning: {} attribute will run as {} " . format ( m , method_name ) )
logger . warning ( f" Collection Warning: { m } attribute will run as { method_name } " )
else :
else :
method_name = m
method_name = m
if method_name in util . show_only_lists and self . library . is_movie :
if method_name in util . show_only_lists and self . library . is_movie :
raise Failed ( " Collection Error: { } attribute only works for show libraries " . format ( method_name ) )
raise Failed ( f " Collection Error: { method_name } attribute only works for show libraries " )
elif method_name in util . movie_only_lists and self . library . is_show :
elif method_name in util . movie_only_lists and self . library . is_show :
raise Failed ( " Collection Error: { } attribute only works for movie libraries " . format ( method_name ) )
raise Failed ( f " Collection Error: { method_name } attribute only works for movie libraries " )
elif method_name in util . movie_only_searches and self . library . is_show :
elif method_name in util . movie_only_searches and self . library . is_show :
raise Failed ( " Collection Error: { } plex search only works for movie libraries " . format ( method_name ) )
raise Failed ( f " Collection Error: { method_name } plex search only works for movie libraries " )
elif method_name not in util . collectionless_lists and self . collectionless :
elif method_name not in util . collectionless_lists and self . collectionless :
raise Failed ( " Collection Error: {} attribute does not work for Collectionless collection " . format ( method_name ) )
raise Failed ( f " Collection Error: { method_name } attribute does not work for Collectionless collection " )
elif method_name == " summary " :
self . summaries [ method_name ] = data [ m ]
elif method_name == " tmdb_summary " :
elif method_name == " tmdb_summary " :
self . details [ " summary " ] = config . TMDb . get_movie_show_or_collection ( util . regex_first_int ( data [ m ] , " TMDb ID " ) , self . library . is_movie ) . overview
self . summaries[ method_name ] = config . TMDb . get_movie_show_or_collection ( util . regex_first_int ( data [ m ] , " TMDb ID " ) , self . library . is_movie ) . overview
elif method_name == " tmdb_description " :
elif method_name == " tmdb_description " :
self . details[ " summary " ] = config . TMDb . get_list ( util . regex_first_int ( data [ m ] , " TMDb List ID " ) ) . description
self . summaries[ method_name ] = config . TMDb . get_list ( util . regex_first_int ( data [ m ] , " TMDb List ID " ) ) . description
elif method_name == " tmdb_biography " :
elif method_name == " tmdb_biography " :
self . details[ " summary " ] = config . TMDb . get_person ( util . regex_first_int ( data [ m ] , " TMDb Person ID " ) ) . biography
self . summaries[ method_name ] = config . TMDb . get_person ( util . regex_first_int ( data [ m ] , " TMDb Person ID " ) ) . biography
elif method_name == " collection_mode " :
elif method_name == " collection_mode " :
if data [ m ] in [ " default " , " hide " , " hide_items " , " show_items " , " hideItems " , " showItems " ] :
if data [ m ] in [ " default " , " hide " , " hide_items " , " show_items " , " hideItems " , " showItems " ] :
if data [ m ] == " hide_items " : self . details [ method_name ] = " hideItems "
if data [ m ] == " hide_items " : self . details [ method_name ] = " hideItems "
elif data [ m ] == " show_items " : self . details [ method_name ] = " showItems "
elif data [ m ] == " show_items " : self . details [ method_name ] = " showItems "
else : self . details [ method_name ] = data [ m ]
else : self . details [ method_name ] = data [ m ]
else :
else :
raise Failed ( " Collection Error: { } collection_mode Invalid \n | \t default (Library default) \n | \t hide (Hide Collection) \n | \t hide_items (Hide Items in this Collection) \n | \t show_items (Show this Collection and its Items) " . format ( data [ m ] ) )
raise Failed ( f " Collection Error: { data [ m ] } collection_mode Invalid \n | \t default (Library default) \n | \t hide (Hide Collection) \n | \t hide_items (Hide Items in this Collection) \n | \t show_items (Show this Collection and its Items) " )
elif method_name == " collection_order " :
elif method_name == " collection_order " :
if data [ m ] in [ " release " , " alpha " ] :
if data [ m ] in [ " release " , " alpha " ] :
self . details [ method_name ] = data [ m ]
self . details [ method_name ] = data [ m ]
else :
else :
raise Failed ( " Collection Error: { } collection_order Invalid \n | \t release (Order Collection by release dates) \n | \t alpha (Order Collection Alphabetically) " . format ( data [ m ] ) )
raise Failed ( f " Collection Error: { data [ m ] } collection_order Invalid \n | \t release (Order Collection by release dates) \n | \t alpha (Order Collection Alphabetically) " )
elif method_name == " url_poster " :
elif method_name == " url_poster " :
self . posters . append ( ( " url " , data [ m ] , method_name ) )
self . posters [ method_name ] = data [ m ]
elif method_name == " tmdb_poster " :
elif method_name == " tmdb_poster " :
self . posters . append ( ( " url " , " {} {} " . format ( config . TMDb . image_url , config . TMDb . get_movie_show_or_collection ( util . regex_first_int ( data [ m ] , " TMDb ID " ) , self . library . is_movie ) . poster_path ) , method_name ) )
self . posters [ method_name ] = f " { config . TMDb . image_url } { config . TMDb . get_movie_show_or_collection ( util . regex_first_int ( data [ m ] , ' TMDb ID ' ) , self . library . is_movie ) . poster_path } "
elif method_name == " tmdb_profile " :
elif method_name == " tmdb_profile " :
self . posters . append ( ( " url " , " {} {} " . format ( config . TMDb . image_url , config . TMDb . get_person ( util . regex_first_int ( data [ m ] , " TMDb Person ID " ) ) . profile_path ) , method_name ) )
self . posters [ method_name ] = f " { config . TMDb . image_url } { config . TMDb . get_person ( util . regex_first_int ( data [ m ] , ' TMDb Person ID ' ) ) . profile_path } "
elif method_name == " file_poster " :
elif method_name == " file_poster " :
if os . path . exists ( data [ m ] ) : self . posters . append ( ( " file " , os . path . abspath ( data [ m ] ) , method_name ) )
if os . path . exists ( data [ m ] ) : self . posters [ method_name ] = os . path . abspath ( data [ m ] )
else : raise Failed ( " Collection Error: Poster Path Does Not Exist: { }" . format ( os . path . abspath ( data [ m ] ) ) )
else : raise Failed ( f " Collection Error: Poster Path Does Not Exist: { os . path . abspath ( data [ m ] ) } " )
elif method_name == " url_background " :
elif method_name == " url_background " :
self . backgrounds . append ( ( " url " , data [ m ] , method_name ) )
self . backgrounds [ method_name ] = data [ m ]
elif method_name == " tmdb_background " :
elif method_name == " tmdb_background " :
self . backgrounds . append ( ( " url " , " {} {} " . format ( config . TMDb . image_url , config . TMDb . get_movie_show_or_collection ( util . regex_first_int ( data [ m ] , " TMDb ID " ) , self . library . is_movie ) . poster_path ) , method_name ) )
self . backgrounds [ method_name ] = f " { config . TMDb . image_url } { config . TMDb . get_movie_show_or_collection ( util . regex_first_int ( data [ m ] , ' TMDb ID ' ) , self . library . is_movie ) . poster_path } "
elif method_name == " file_background " :
elif method_name == " file_background " :
if os . path . exists ( data [ m ] ) : self . backgrounds . append ( ( " file " , os . path . abspath ( data [ m ] ) , method_name ) )
if os . path . exists ( data [ m ] ) : self . backgrounds [ method_name ] = os . path . abspath ( data [ m ] )
else : raise Failed ( " Collection Error: Background Path Does Not Exist: { }" . format ( os . path . abspath ( data [ m ] ) ) )
else : raise Failed ( f " Collection Error: Background Path Does Not Exist: { os . path . abspath ( data [ m ] ) } " )
elif method_name == " label_sync_mode " :
elif method_name == " label_sync_mode " :
if data [ m ] in [ " append " , " sync " ] : self . details [ method_name ] = data [ m ]
if data [ m ] in [ " append " , " sync " ] : self . details [ method_name ] = data [ m ]
else : raise Failed ( " Collection Error: label_sync_mode attribute must be either ' append ' or ' sync ' " )
else : raise Failed ( " Collection Error: label_sync_mode attribute must be either ' append ' or ' sync ' " )
elif method_name == " sync_mode " :
if data [ m ] in [ " append " , " sync " ] : self . details [ method_name ] = data [ m ]
else : raise Failed ( " Collection Error: sync_mode attribute must be either ' append ' or ' sync ' " )
elif method_name in [ " arr_tag " , " label " ] :
elif method_name in [ " arr_tag " , " label " ] :
self . details [ method_name ] = util . get_list ( data [ m ] )
self . details [ method_name ] = util . get_list ( data [ m ] )
elif method_name in util . boolean_details :
elif method_name in util . boolean_details :
if isinstance ( data [ m ] , bool ) : self . details [ method_name ] = data [ m ]
if isinstance ( data [ m ] , bool ) : self . details [ method_name ] = data [ m ]
else : raise Failed ( " Collection Error: { } attribute must be either true or false " . format ( method_name ) )
else : raise Failed ( f " Collection Error: { method_name } attribute must be either true or false " )
elif method_name in util . all_details :
elif method_name in util . all_details :
self . details [ method_name ] = data [ m ]
self . details [ method_name ] = data [ m ]
elif method_name in [ " year " , " year.not " ] :
elif method_name in [ " year " , " year.not " ] :
@ -293,7 +318,6 @@ class CollectionBuilder:
elif method_name == " imdb_list " :
elif method_name == " imdb_list " :
new_list = [ ]
new_list = [ ]
for imdb_list in util . get_list ( data [ m ] , split = False ) :
for imdb_list in util . get_list ( data [ m ] , split = False ) :
new_dictionary = { }
if isinstance ( imdb_list , dict ) :
if isinstance ( imdb_list , dict ) :
if " url " in imdb_list and imdb_list [ " url " ] : imdb_url = imdb_list [ " url " ]
if " url " in imdb_list and imdb_list [ " url " ] : imdb_url = imdb_list [ " url " ]
else : raise Failed ( " Collection Error: imdb_list attribute url is required " )
else : raise Failed ( " Collection Error: imdb_list attribute url is required " )
@ -305,25 +329,44 @@ class CollectionBuilder:
self . methods . append ( ( method_name , new_list ) )
self . methods . append ( ( method_name , new_list ) )
elif method_name in util . dictionary_lists :
elif method_name in util . dictionary_lists :
if isinstance ( data [ m ] , dict ) :
if isinstance ( data [ m ] , dict ) :
def get_int ( parent , method , data , default , min = 1 , max = None ) :
def get_int ( parent , method , data _in, default_in , minimum = 1 , maximum = None ) :
if method not in data : logger . warning ( " Collection Warning: {} {} attribute not found using {} as default " . format ( parent , method , default ) )
if method not in data _in : logger . warning ( f" Collection Warning: { parent } { method } attribute not found using { default } as default " )
elif not data [ method ] : logger . warning ( " Collection Warning: {} {} attribute is blank using {} as default " . format ( parent , method , default ) )
elif not data _in [ method ] : logger . warning ( f" Collection Warning: { parent } { method } attribute is blank using { default } as default " )
elif isinstance ( data [ method ] , int ) and data [ method ] > = min :
elif isinstance ( data _in [ method ] , int ) and data _in [ method ] > = minimum :
if max is None or data [ method ] < = max : return data [ method ]
if maximum is None or data_in [ method ] < = maximum : return data_in [ method ]
else : logger . warning ( " Collection Warning: {} {} attribute {} invalid must an integer <= {} using {} as default " . format ( parent , method , data [ method ] , max , default ) )
else : logger . warning ( f" Collection Warning: { parent } { method } attribute { data_in [ method ] } invalid must an integer <= { maximum } using { default } as default " )
else : logger . warning ( " Collection Warning: {} {} attribute {} invalid must an integer >= {} using {} as default " . format ( parent , method , data [ method ] , min , default ) )
else : logger . warning ( f" Collection Warning: { parent } { method } attribute { data_in [ method ] } invalid must an integer >= { minimum } using { default } as default " )
return default
return default _in
if method_name == " filters " :
if method_name == " filters " :
for f in data [ m ] :
for f in data [ m ] :
if f in util . method_alias or ( f . endswith ( " .not " ) and f [ : - 4 ] in util . method_alias ) :
if f in util . method_alias or ( f . endswith ( " .not " ) and f [ : - 4 ] in util . method_alias ) :
filter = ( util . method_alias [ f [ : - 4 ] ] + f [ - 4 : ] ) if f . endswith ( " .not " ) else util . method_alias [ f ]
filter_method = ( util . method_alias [ f [ : - 4 ] ] + f [ - 4 : ] ) if f . endswith ( " .not " ) else util . method_alias [ f ]
logger . warning ( " Collection Warning: {} filter will run as {} " . format ( f , filter ) )
logger . warning ( f " Collection Warning: { f } filter will run as { filter_method } " )
else :
else :
filter = f
filter_method = f
if filter in util . movie_only_filters and self . library . is_show : raise Failed ( " Collection Error: {} filter only works for movie libraries " . format ( filter ) )
if filter_method in util . movie_only_filters and self . library . is_show :
elif data [ m ] [ f ] is None : raise Failed ( " Collection Error: {} filter is blank " . format ( filter ) )
raise Failed ( f " Collection Error: { filter_method } filter only works for movie libraries " )
elif filter in util . all_filters : self . filters . append ( ( filter , data [ m ] [ f ] ) )
elif data [ m ] [ f ] is None :
else : raise Failed ( " Collection Error: {} filter not supported " . format ( filter ) )
raise Failed ( f " Collection Error: { filter_method } filter is blank " )
elif filter_method == " year " :
filter_data = util . get_year_list ( data [ m ] [ f ] , f " { filter_method } filter " )
elif filter_method in [ " max_age " , " duration.gte " , " duration.lte " , " tmdb_vote_count.gte " , " tmdb_vote_count.lte " ] :
filter_data = util . check_number ( data [ m ] [ f ] , f " { filter_method } filter " , minimum = 1 )
elif filter_method in [ " year.gte " , " year.lte " ] :
filter_data = util . check_number ( data [ m ] [ f ] , f " { filter_method } filter " , minimum = 1800 , maximum = current_year )
elif filter_method in [ " rating.gte " , " rating.lte " ] :
filter_data = util . check_number ( data [ m ] [ f ] , f " { filter_method } filter " , number_type = " float " , minimum = 0.1 , maximum = 10 )
elif filter_method in [ " originally_available.gte " , " originally_available.lte " ] :
filter_data = util . check_date ( data [ m ] [ f ] , f " { filter_method } filter " )
elif filter_method == " original_language " :
filter_data = util . get_list ( data [ m ] [ f ] , lower = True )
elif filter_method == " collection " :
filter_data = data [ m ] [ f ] if isinstance ( data [ m ] [ f ] , list ) else [ data [ m ] [ f ] ]
elif filter_method in util . all_filters :
filter_data = util . get_list ( data [ m ] [ f ] )
else :
raise Failed ( f " Collection Error: { filter_method } filter not supported " )
self . filters . append ( ( filter_method , filter_data ) )
elif method_name == " plex_collectionless " :
elif method_name == " plex_collectionless " :
new_dictionary = { }
new_dictionary = { }
prefix_list = [ ]
prefix_list = [ ]
@ -347,13 +390,13 @@ class CollectionBuilder:
for s in data [ m ] :
for s in data [ m ] :
if s in util . method_alias or ( s . endswith ( " .not " ) and s [ : - 4 ] in util . method_alias ) :
if s in util . method_alias or ( s . endswith ( " .not " ) and s [ : - 4 ] in util . method_alias ) :
search = ( util . method_alias [ s [ : - 4 ] ] + s [ - 4 : ] ) if s . endswith ( " .not " ) else util . method_alias [ s ]
search = ( util . method_alias [ s [ : - 4 ] ] + s [ - 4 : ] ) if s . endswith ( " .not " ) else util . method_alias [ s ]
logger . warning ( " Collection Warning: { } plex search attribute will run as { }" . format ( s , search ) )
logger . warning ( f " Collection Warning: { s } plex search attribute will run as { search } " )
else :
else :
search = s
search = s
if search in util . movie_only_searches and self . library . is_show :
if search in util . movie_only_searches and self . library . is_show :
raise Failed ( " Collection Error: { } plex search attribute only works for movie libraries " . format ( search ) )
raise Failed ( f " Collection Error: { search } plex search attribute only works for movie libraries " )
elif util . remove_not ( search ) in used :
elif util . remove_not ( search ) in used :
raise Failed ( " Collection Error: Only one instance of { } can be used try using it as a filter instead " . format ( search ) )
raise Failed ( f " Collection Error: Only one instance of { search } can be used try using it as a filter instead " )
elif search in [ " year " , " year.not " ] :
elif search in [ " year " , " year.not " ] :
years = util . get_year_list ( data [ m ] [ s ] , search )
years = util . get_year_list ( data [ m ] [ s ] , search )
if len ( years ) > 0 :
if len ( years ) > 0 :
@ -363,7 +406,7 @@ class CollectionBuilder:
used . append ( util . remove_not ( search ) )
used . append ( util . remove_not ( search ) )
searches . append ( ( search , util . get_list ( data [ m ] [ s ] ) ) )
searches . append ( ( search , util . get_list ( data [ m ] [ s ] ) ) )
else :
else :
logger . error ( " Collection Error: { } plex search attribute not supported " . format ( search ) )
logger . error ( f " Collection Error: { search } plex search attribute not supported " )
self . methods . append ( ( method_name , [ searches ] ) )
self . methods . append ( ( method_name , [ searches ] ) )
elif method_name == " tmdb_discover " :
elif method_name == " tmdb_discover " :
new_dictionary = { " limit " : 100 }
new_dictionary = { " limit " : 100 }
@ -375,71 +418,58 @@ class CollectionBuilder:
if re . compile ( " ([a-z] {2} )-([A-Z] {2} ) " ) . match ( str ( attr_data ) ) :
if re . compile ( " ([a-z] {2} )-([A-Z] {2} ) " ) . match ( str ( attr_data ) ) :
new_dictionary [ attr ] = str ( attr_data )
new_dictionary [ attr ] = str ( attr_data )
else :
else :
raise Failed ( " Collection Error: {} attribute {} : {} must match pattern ([a-z] {2} )-([A-Z] {2} ) e.g. en-US " . format ( m , attr , attr_data ) )
raise Failed ( f" Collection Error: { m } attribute { attr } : { attr_data } must match pattern ([a-z] {{ 2 }} )-([A-Z] {{ 2 }} ) e.g. en-US " )
elif attr == " region " :
elif attr == " region " :
if re . compile ( " ^[A-Z] {2} $ " ) . match ( str ( attr_data ) ) :
if re . compile ( " ^[A-Z] {2} $ " ) . match ( str ( attr_data ) ) :
new_dictionary [ attr ] = str ( attr_data )
new_dictionary [ attr ] = str ( attr_data )
else :
else :
raise Failed ( " Collection Error: {} attribute {} : {} must match pattern ^[A-Z] {2} $ e.g. US " . format ( m , attr , attr_data ) )
raise Failed ( f" Collection Error: { m } attribute { attr } : { attr_data } must match pattern ^[A-Z] {{ 2 }} $ e.g. US " )
elif attr == " sort_by " :
elif attr == " sort_by " :
if ( self . library . is_movie and attr_data in util . discover_movie_sort ) or ( self . library . is_show and attr_data in util . discover_tv_sort ) :
if ( self . library . is_movie and attr_data in util . discover_movie_sort ) or ( self . library . is_show and attr_data in util . discover_tv_sort ) :
new_dictionary [ attr ] = attr_data
new_dictionary [ attr ] = attr_data
else :
else :
raise Failed ( " Collection Error: { } attribute { }: { } is invalid " . format ( m , attr , attr_data ) )
raise Failed ( f " Collection Error: { m } attribute { attr }: { attr_data } is invalid " )
elif attr == " certification_country " :
elif attr == " certification_country " :
if " certification " in data [ m ] or " certification.lte " in data [ m ] or " certification.gte " in data [ m ] :
if " certification " in data [ m ] or " certification.lte " in data [ m ] or " certification.gte " in data [ m ] :
new_dictionary [ attr ] = attr_data
new_dictionary [ attr ] = attr_data
else :
else :
raise Failed ( " Collection Error: { } attribute { }: must be used with either certification, certification.lte, or certification.gte " . format ( m , attr ) )
raise Failed ( f " Collection Error: { m } attribute { attr }: must be used with either certification, certification.lte, or certification.gte " )
elif attr in [ " certification " , " certification.lte " , " certification.gte " ] :
elif attr in [ " certification " , " certification.lte " , " certification.gte " ] :
if " certification_country " in data [ m ] :
if " certification_country " in data [ m ] :
new_dictionary [ attr ] = attr_data
new_dictionary [ attr ] = attr_data
else :
else :
raise Failed ( " Collection Error: { } attribute { }: must be used with certification_country " . format ( m , attr ) )
raise Failed ( f " Collection Error: { m } attribute { attr }: must be used with certification_country " )
elif attr in [ " include_adult " , " include_null_first_air_dates " , " screened_theatrically " ] :
elif attr in [ " include_adult " , " include_null_first_air_dates " , " screened_theatrically " ] :
if attr_data is True :
if attr_data is True :
new_dictionary [ attr ] = attr_data
new_dictionary [ attr ] = attr_data
elif attr in [ " primary_release_date.gte " , " primary_release_date.lte " , " release_date.gte " , " release_date.lte " , " air_date.gte " , " air_date.lte " , " first_air_date.gte " , " first_air_date.lte " ] :
elif attr in [ " primary_release_date.gte " , " primary_release_date.lte " , " release_date.gte " , " release_date.lte " , " air_date.gte " , " air_date.lte " , " first_air_date.gte " , " first_air_date.lte " ] :
if re . compile ( " [0-1]?[0-9][/-][0-3]?[0-9][/-][1-2][890][0-9][0-9] " ) . match ( str ( attr_data ) ) :
new_dictionary [ attr ] = util . check_date ( attr_data , f " { m } attribute { attr } " , return_string = True )
the_date = str ( attr_data ) . split ( " / " ) if " / " in str ( attr_data ) else str ( attr_data ) . split ( " - " )
new_dictionary [ attr ] = " {} - {} - {} " . format ( the_date [ 2 ] , the_date [ 0 ] , the_date [ 1 ] )
elif re . compile ( " [1-2][890][0-9][0-9][/-][0-1]?[0-9][/-][0-3]?[0-9] " ) . match ( str ( attr_data ) ) :
the_date = str ( attr_data ) . split ( " / " ) if " / " in str ( attr_data ) else str ( attr_data ) . split ( " - " )
new_dictionary [ attr ] = " {} - {} - {} " . format ( the_date [ 0 ] , the_date [ 1 ] , the_date [ 2 ] )
else :
raise Failed ( " Collection Error: {} attribute {} : {} must match pattern MM/DD/YYYY e.g. 12/25/2020 " . format ( m , attr , attr_data ) )
elif attr in [ " primary_release_year " , " year " , " first_air_date_year " ] :
elif attr in [ " primary_release_year " , " year " , " first_air_date_year " ] :
if isinstance ( attr_data , int ) and 1800 < attr_data and attr_data < 2200 :
new_dictionary [ attr ] = util . check_number ( attr_data , f " { m } attribute { attr } " , minimum = 1800 , maximum = current_year + 1 )
new_dictionary [ attr ] = attr_data
else :
raise Failed ( " Collection Error: {} attribute {} : must be a valid year e.g. 1990 " . format ( m , attr ) )
elif attr in [ " vote_count.gte " , " vote_count.lte " , " vote_average.gte " , " vote_average.lte " , " with_runtime.gte " , " with_runtime.lte " ] :
elif attr in [ " vote_count.gte " , " vote_count.lte " , " vote_average.gte " , " vote_average.lte " , " with_runtime.gte " , " with_runtime.lte " ] :
if ( isinstance ( attr_data , int ) or isinstance ( attr_data , float ) ) and 0 < attr_data :
new_dictionary [ attr ] = util . check_number ( attr_data , f " { m } attribute { attr } " , minimum = 1 )
new_dictionary [ attr ] = attr_data
else :
raise Failed ( " Collection Error: {} attribute {} : must be a valid number greater then 0 " . format ( m , attr ) )
elif attr in [ " with_cast " , " with_crew " , " with_people " , " with_companies " , " with_networks " , " with_genres " , " without_genres " , " with_keywords " , " without_keywords " , " with_original_language " , " timezone " ] :
elif attr in [ " with_cast " , " with_crew " , " with_people " , " with_companies " , " with_networks " , " with_genres " , " without_genres " , " with_keywords " , " without_keywords " , " with_original_language " , " timezone " ] :
new_dictionary [ attr ] = attr_data
new_dictionary [ attr ] = attr_data
else :
else :
raise Failed ( " Collection Error: { } attribute { } not supported " . format ( m , attr ) )
raise Failed ( f " Collection Error: { m } attribute { attr } not supported " )
elif attr == " limit " :
elif attr == " limit " :
if isinstance ( attr_data , int ) and attr_data > 0 :
if isinstance ( attr_data , int ) and attr_data > 0 :
new_dictionary [ attr ] = attr_data
new_dictionary [ attr ] = attr_data
else :
else :
raise Failed ( " Collection Error: { } attribute { }: must be a valid number greater then 0 " . format ( m , attr ) )
raise Failed ( f " Collection Error: { m } attribute { attr }: must be a valid number greater then 0 " )
else :
else :
raise Failed ( " Collection Error: { } attribute { } not supported " . format ( m , attr ) )
raise Failed ( f " Collection Error: { m } attribute { attr } not supported " )
else :
else :
raise Failed ( " Collection Error: { } parameter { } is blank " . format ( m , attr ) )
raise Failed ( f " Collection Error: { m } parameter { attr } is blank " )
if len ( new_dictionary ) > 1 :
if len ( new_dictionary ) > 1 :
self . methods . append ( ( method_name , [ new_dictionary ] ) )
self . methods . append ( ( method_name , [ new_dictionary ] ) )
else :
else :
raise Failed ( " Collection Error: { } had no valid fields " . format ( m ) )
raise Failed ( f " Collection Error: { m } had no valid fields " )
elif " tautulli " in method_name :
elif " tautulli " in method_name :
new_dictionary = { }
new_dictionary = { }
if method_name == " tautulli_popular " : new_dictionary [ " list_type " ] = " popular "
if method_name == " tautulli_popular " : new_dictionary [ " list_type " ] = " popular "
elif method_name == " tautulli_watched " : new_dictionary [ " list_type " ] = " watched "
elif method_name == " tautulli_watched " : new_dictionary [ " list_type " ] = " watched "
else : raise Failed ( " Collection Error: { } attribute not supported " . format ( method_name ) )
else : raise Failed ( f " Collection Error: { method_name } attribute not supported " )
new_dictionary [ " list_days " ] = get_int ( method_name , " list_days " , data [ m ] , 30 )
new_dictionary [ " list_days " ] = get_int ( method_name , " list_days " , data [ m ] , 30 )
new_dictionary [ " list_size " ] = get_int ( method_name , " list_size " , data [ m ] , 10 )
new_dictionary [ " list_size " ] = get_int ( method_name , " list_size " , data [ m ] , 10 )
@ -449,22 +479,21 @@ class CollectionBuilder:
new_dictionary = { " sort_by " : " anime_num_list_users " }
new_dictionary = { " sort_by " : " anime_num_list_users " }
if " sort_by " not in data [ m ] : logger . warning ( " Collection Warning: mal_season sort_by attribute not found using members as default " )
if " sort_by " not in data [ m ] : logger . warning ( " Collection Warning: mal_season sort_by attribute not found using members as default " )
elif not data [ m ] [ " sort_by " ] : logger . warning ( " Collection Warning: mal_season sort_by attribute is blank using members as default " )
elif not data [ m ] [ " sort_by " ] : logger . warning ( " Collection Warning: mal_season sort_by attribute is blank using members as default " )
elif data [ m ] [ " sort_by " ] not in util . mal_season_sort : logger . warning ( " Collection Warning: mal_season sort_by attribute { } invalid must be either ' members ' or ' score ' using members as default " . format ( data [ m ] [ " sort_by " ] ) )
elif data [ m ] [ " sort_by " ] not in util . mal_season_sort : logger . warning ( f " Collection Warning: mal_season sort_by attribute { data [ m ] [ ' sort_by ' ] } invalid must be either ' members ' or ' score ' using members as default " )
else : new_dictionary [ " sort_by " ] = util . mal_season_sort [ data [ m ] [ " sort_by " ] ]
else : new_dictionary [ " sort_by " ] = util . mal_season_sort [ data [ m ] [ " sort_by " ] ]
current_time = datetime . now ( )
if current_time . month in [ 1 , 2 , 3 ] : new_dictionary [ " season " ] = " winter "
if current_time . month in [ 1 , 2 , 3 ] : new_dictionary [ " season " ] = " winter "
elif current_time . month in [ 4 , 5 , 6 ] : new_dictionary [ " season " ] = " spring "
elif current_time . month in [ 4 , 5 , 6 ] : new_dictionary [ " season " ] = " spring "
elif current_time . month in [ 7 , 8 , 9 ] : new_dictionary [ " season " ] = " summer "
elif current_time . month in [ 7 , 8 , 9 ] : new_dictionary [ " season " ] = " summer "
elif current_time . month in [ 10 , 11 , 12 ] : new_dictionary [ " season " ] = " fall "
elif current_time . month in [ 10 , 11 , 12 ] : new_dictionary [ " season " ] = " fall "
if " season " not in data [ m ] : logger . warning ( " Collection Warning: mal_season season attribute not found using the current season: { } as default " . format ( new_dictionary [ " season " ] ) )
if " season " not in data [ m ] : logger . warning ( f " Collection Warning: mal_season season attribute not found using the current season: { new_dictionary [ ' season ' ] } as default " )
elif not data [ m ] [ " season " ] : logger . warning ( " Collection Warning: mal_season season attribute is blank using the current season: { } as default " . format ( new_dictionary [ " season " ] ) )
elif not data [ m ] [ " season " ] : logger . warning ( f " Collection Warning: mal_season season attribute is blank using the current season: { new_dictionary [ ' season ' ] } as default " )
elif data [ m ] [ " season " ] not in util . pretty_seasons : logger . warning ( " Collection Warning: mal_season season attribute { } invalid must be either ' winter ' , ' spring ' , ' summer ' or ' fall ' using the current season: { } as default " . format ( data [ m ] [ " season " ] , new_dictionary [ " season " ] ) )
elif data [ m ] [ " season " ] not in util . pretty_seasons : logger . warning ( f " Collection Warning: mal_season season attribute { data [ m ] [ ' season ' ] } invalid must be either ' winter ' , ' spring ' , ' summer ' or ' fall ' using the current season: { new_dictionary [ ' season ' ] } as default " )
else : new_dictionary [ " season " ] = data [ m ] [ " season " ]
else : new_dictionary [ " season " ] = data [ m ] [ " season " ]
new_dictionary [ " year " ] = get_int ( method_name , " year " , data [ m ] , current_time . year , min = 1917 , max = current_time . year + 1 )
new_dictionary [ " year " ] = get_int ( method_name , " year " , data [ m ] , current_time . year , minimum = 1917 , maximum = current_time . year + 1 )
new_dictionary [ " limit " ] = get_int ( method_name , " limit " , data [ m ] , 100 , max = 500 )
new_dictionary [ " limit " ] = get_int ( method_name , " limit " , data [ m ] , 100 , maximum = 500 )
self . methods . append ( ( method_name , [ new_dictionary ] ) )
self . methods . append ( ( method_name , [ new_dictionary ] ) )
elif method_name == " mal_userlist " :
elif method_name == " mal_userlist " :
new_dictionary = { " status " : " all " , " sort_by " : " list_score " }
new_dictionary = { " status " : " all " , " sort_by " : " list_score " }
@ -474,48 +503,60 @@ class CollectionBuilder:
if " status " not in data [ m ] : logger . warning ( " Collection Warning: mal_season status attribute not found using all as default " )
if " status " not in data [ m ] : logger . warning ( " Collection Warning: mal_season status attribute not found using all as default " )
elif not data [ m ] [ " status " ] : logger . warning ( " Collection Warning: mal_season status attribute is blank using all as default " )
elif not data [ m ] [ " status " ] : logger . warning ( " Collection Warning: mal_season status attribute is blank using all as default " )
elif data [ m ] [ " status " ] not in util . mal_userlist_status : logger . warning ( " Collection Warning: mal_season status attribute { } invalid must be either ' all ' , ' watching ' , ' completed ' , ' on_hold ' , ' dropped ' or ' plan_to_watch ' using all as default " . format ( data [ m ] [ " status " ] ) )
elif data [ m ] [ " status " ] not in util . mal_userlist_status : logger . warning ( f " Collection Warning: mal_season status attribute { data [ m ] [ ' status ' ] } invalid must be either ' all ' , ' watching ' , ' completed ' , ' on_hold ' , ' dropped ' or ' plan_to_watch ' using all as default " )
else : new_dictionary [ " status " ] = util . mal_userlist_status [ data [ m ] [ " status " ] ]
else : new_dictionary [ " status " ] = util . mal_userlist_status [ data [ m ] [ " status " ] ]
if " sort_by " not in data [ m ] : logger . warning ( " Collection Warning: mal_season sort_by attribute not found using score as default " )
if " sort_by " not in data [ m ] : logger . warning ( " Collection Warning: mal_season sort_by attribute not found using score as default " )
elif not data [ m ] [ " sort_by " ] : logger . warning ( " Collection Warning: mal_season sort_by attribute is blank using score as default " )
elif not data [ m ] [ " sort_by " ] : logger . warning ( " Collection Warning: mal_season sort_by attribute is blank using score as default " )
elif data [ m ] [ " sort_by " ] not in util . mal_userlist_sort : logger . warning ( " Collection Warning: mal_season sort_by attribute { } invalid must be either ' score ' , ' last_updated ' , ' title ' or ' start_date ' using score as default " . format ( data [ m ] [ " sort_by " ] ) )
elif data [ m ] [ " sort_by " ] not in util . mal_userlist_sort : logger . warning ( f " Collection Warning: mal_season sort_by attribute { data [ m ] [ ' sort_by ' ] } invalid must be either ' score ' , ' last_updated ' , ' title ' or ' start_date ' using score as default " )
else : new_dictionary [ " sort_by " ] = util . mal_userlist_sort [ data [ m ] [ " sort_by " ] ]
else : new_dictionary [ " sort_by " ] = util . mal_userlist_sort [ data [ m ] [ " sort_by " ] ]
new_dictionary [ " limit " ] = get_int ( method_name , " limit " , data [ m ] , 100 , max = 1000 )
new_dictionary [ " limit " ] = get_int ( method_name , " limit " , data [ m ] , 100 , maximum = 1000 )
self . methods . append ( ( method_name , [ new_dictionary ] ) )
self . methods . append ( ( method_name , [ new_dictionary ] ) )
else :
else :
raise Failed ( " Collection Error: { } attribute is not a dictionary: { }" . format ( m , data [ m ] ) )
raise Failed ( f " Collection Error: { m } attribute is not a dictionary: { data [ m ] } " )
elif method_name in util . count_lists :
elif method_name in util . count_lists :
list_count = util . regex_first_int ( data [ m ] , " List Size " , default = 20 )
list_count = util . regex_first_int ( data [ m ] , " List Size " , default = 20 )
if list_count < 1 :
if list_count < 1 :
logger . warning ( " Collection Warning: { } must be an integer greater then 0 defaulting to 20 " . format ( method_name ) )
logger . warning ( f " Collection Warning: { method_name } must be an integer greater then 0 defaulting to 20 " )
list_count = 20
list_count = 20
self . methods . append ( ( method_name , [ list_count ] ) )
self . methods . append ( ( method_name , [ list_count ] ) )
elif method_name in util . tmdb_lists :
elif method_name in util . tmdb_lists :
values = config . TMDb . validate_tmdb_list ( util . get_int_list ( data [ m ] , " TMDb { } ID " . format ( util . tmdb_type [ method_name ] ) ) , util . tmdb_type [ method_name ] )
values = config . TMDb . validate_tmdb_list ( util . get_int_list ( data [ m ] , f " TMDb { util . tmdb_type [ method_name ] } ID " ) , util . tmdb_type [ method_name ] )
if method_name [ - 8 : ] == " _details " :
if method_name [ - 8 : ] == " _details " :
if method_name in [ " tmdb_collection_details " , " tmdb_movie_details " , " tmdb_show_details " ] :
if method_name in [ " tmdb_collection_details " , " tmdb_movie_details " , " tmdb_show_details " ] :
item = config . TMDb . get_movie_show_or_collection ( values [ 0 ] , self . library . is_movie )
item = config . TMDb . get_movie_show_or_collection ( values [ 0 ] , self . library . is_movie )
if " summary " not in self . details and hasattr ( item , " overview " ) and item . overview :
if hasattr ( item , " overview " ) and item . overview :
self . details [ " summary " ] = item . overview
self . summaries [ method_name ] = item . overview
if " background " not in self . details and hasattr ( item , " backdrop_path " ) and item . backdrop_path :
if hasattr ( item , " backdrop_path " ) and item . backdrop_path :
self . details [ " background " ] = ( " url " , " {} {} " . format ( config . TMDb . image_url , item . backdrop_path ) , method_name [ : - 8 ] )
self . backgrounds [ method_name ] = f " { config . TMDb . image_url } { item . backdrop_path } "
if " poster " not in self . details and hasattr ( item , " poster_path " ) and item . poster_path :
if hasattr ( item , " poster_path " ) and item . poster_path :
self . details [ " poster " ] = ( " url " , " {} {} " . format ( config . TMDb . image_url , item . poster_path ) , method_name [ : - 8 ] )
self . posters [ method_name ] = f " { config . TMDb . image_url } { item . poster_path } "
elif method_name in [ " tmdb_actor_details " , " tmdb_crew_details " , " tmdb_director_details " , " tmdb_producer_details " , " tmdb_writer_details " ] :
item = config . TMDb . get_person ( values [ 0 ] )
if hasattr ( item , " biography " ) and item . biography :
self . summaries [ method_name ] = item . biography
if hasattr ( item , " profile_path " ) and item . profile_path :
self . posters [ method_name ] = f " { config . TMDb . image_url } { item . profile_path } "
else :
else :
item = config . TMDb . get_list ( values [ 0 ] )
item = config . TMDb . get_list ( values [ 0 ] )
if " summary " not in self . details and hasattr ( item , " description " ) and item . description :
if hasattr ( item , " description " ) and item . description :
self . details [ " summary " ] = item . description
self . summaries[ method_name ] = item . description
self . methods . append ( ( method_name [ : - 8 ] , values ) )
self . methods . append ( ( method_name [ : - 8 ] , values ) )
else :
else :
self . methods . append ( ( method_name , values ) )
self . methods . append ( ( method_name , values ) )
elif method_name in util . all_lists :
elif method_name in util . all_lists :
self . methods . append ( ( method_name , util . get_list ( data [ m ] ) ) )
self . methods . append ( ( method_name , util . get_list ( data [ m ] ) ) )
elif method_name not in util . other_attributes :
elif method_name not in util . other_attributes :
raise Failed ( " Collection Error: { } attribute not supported " . format ( method_name ) )
raise Failed ( f " Collection Error: { method_name } attribute not supported " )
else :
else :
raise Failed ( " Collection Error: {} attribute is blank " . format ( m ) )
raise Failed ( f " Collection Error: { m } attribute is blank " )
self . sync = self . library . sync_mode == " sync "
if " sync_mode " in data :
if not data [ " sync_mode " ] : logger . warning ( f " Collection Warning: sync_mode attribute is blank using general: { self . library . sync_mode } " )
elif data [ " sync_mode " ] not in [ " append " , " sync " ] : logger . warning ( f " Collection Warning: { self . library . sync_mode } sync_mode invalid using general: { data [ ' sync_mode ' ] } " )
else : self . sync = data [ " sync_mode " ] == " sync "
self . do_arr = False
self . do_arr = False
if self . library . Radarr :
if self . library . Radarr :
@ -523,12 +564,12 @@ class CollectionBuilder:
if self . library . Sonarr :
if self . library . Sonarr :
self . do_arr = self . details [ " add_to_arr " ] if " add_to_arr " in self . details else self . library . Sonarr . add
self . do_arr = self . details [ " add_to_arr " ] if " add_to_arr " in self . details else self . library . Sonarr . add
def run_methods ( self , collection_obj , collection_name , map, movie_map , show_map ) :
def run_methods ( self , collection_obj , collection_name , rating_key_ map, movie_map , show_map ) :
items_found = 0
items_found = 0
for method , values in self . methods :
for method , values in self . methods :
logger . debug ( " " )
logger . debug ( " " )
logger . debug ( " Method: {} " . format ( method ) )
logger . debug ( f" Method: { method } " )
logger . debug ( " Values: {} " . format ( values ) )
logger . debug ( f" Values: { values } " )
pretty = util . pretty_names [ method ] if method in util . pretty_names else method
pretty = util . pretty_names [ method ] if method in util . pretty_names else method
for value in values :
for value in values :
items = [ ]
items = [ ]
@ -549,9 +590,9 @@ class CollectionBuilder:
else : missing_shows . append ( show_id )
else : missing_shows . append ( show_id )
return items_found_inside
return items_found_inside
logger . info ( " " )
logger . info ( " " )
logger . debug ( " Value: {} " . format ( value ) )
logger . debug ( f" Value: { value } " )
if method == " plex_all " :
if method == " plex_all " :
logger . info ( " Processing {} {} " . format ( pretty , " Movies " if self . library . is_movie else " Shows " ) )
logger . info ( f" Processing { pretty } { ' Movies ' if self . library . is_movie else ' Shows ' } " )
items = self . library . Plex . all ( )
items = self . library . Plex . all ( )
items_found + = len ( items )
items_found + = len ( items )
elif method == " plex_collection " :
elif method == " plex_collection " :
@ -559,7 +600,6 @@ class CollectionBuilder:
items_found + = len ( items )
items_found + = len ( items )
elif method == " plex_search " :
elif method == " plex_search " :
search_terms = { }
search_terms = { }
output = " "
for i , attr_pair in enumerate ( value ) :
for i , attr_pair in enumerate ( value ) :
search_list = attr_pair [ 1 ]
search_list = attr_pair [ 1 ]
final_method = attr_pair [ 0 ] [ : - 4 ] + " ! " if attr_pair [ 0 ] [ - 4 : ] == " .not " else attr_pair [ 0 ]
final_method = attr_pair [ 0 ] [ : - 4 ] + " ! " if attr_pair [ 0 ] [ - 4 : ] == " .not " else attr_pair [ 0 ]
@ -568,8 +608,9 @@ class CollectionBuilder:
search_terms [ final_method ] = search_list
search_terms [ final_method ] = search_list
ors = " "
ors = " "
for o , param in enumerate ( attr_pair [ 1 ] ) :
for o , param in enumerate ( attr_pair [ 1 ] ) :
ors + = " {} {} " . format ( " OR " if o > 0 else " {} ( " . format ( attr_pair [ 0 ] ) , param )
or_des = " OR " if o > 0 else f " { attr_pair [ 0 ] } ( "
logger . info ( " \t \t AND {} ) " . format ( ors ) if i > 0 else " Processing {} : {} ) " . format ( pretty , ors ) )
ors + = f " { or_des } { param } "
logger . info ( f " \t \t AND { ors } ) " if i > 0 else f " Processing { pretty } : { ors } ) " )
items = self . library . Plex . search ( * * search_terms )
items = self . library . Plex . search ( * * search_terms )
items_found + = len ( items )
items_found + = len ( items )
elif method == " plex_collectionless " :
elif method == " plex_collectionless " :
@ -590,7 +631,7 @@ class CollectionBuilder:
all_items = self . library . Plex . all ( )
all_items = self . library . Plex . all ( )
length = 0
length = 0
for i , item in enumerate ( all_items , 1 ) :
for i , item in enumerate ( all_items , 1 ) :
length = util . print_return ( length , " Processing: {} / {} {} " . format ( i , len ( all_items ) , item . title ) )
length = util . print_return ( length , f" Processing: { i } / { len ( all_items ) } { item . title } " )
add_item = True
add_item = True
for collection in item . collections :
for collection in item . collections :
if collection . tag . lower ( ) in good_collections :
if collection . tag . lower ( ) in good_collections :
@ -599,7 +640,7 @@ class CollectionBuilder:
if add_item :
if add_item :
items . append ( item )
items . append ( item )
items_found + = len ( items )
items_found + = len ( items )
util . print_end ( length , " Processed { } {} " . format ( len ( all_items ) , " Movies " if self . library . is_movie else " Shows " ) )
util . print_end ( length , f " Processed { len ( all_items ) } { ' Movies ' if self . library . is_movie else ' Shows ' } " )
elif " tautulli " in method :
elif " tautulli " in method :
items = self . library . Tautulli . get_items ( self . library , time_range = value [ " list_days " ] , stats_count = value [ " list_size " ] , list_type = value [ " list_type " ] , stats_count_buffer = value [ " list_buffer " ] )
items = self . library . Tautulli . get_items ( self . library , time_range = value [ " list_days " ] , stats_count = value [ " list_size " ] , list_type = value [ " list_type " ] , stats_count_buffer = value [ " list_buffer " ] )
items_found + = len ( items )
items_found + = len ( items )
@ -609,98 +650,145 @@ class CollectionBuilder:
elif " imdb " in method : items_found + = check_map ( self . config . IMDb . get_items ( method , value , self . library . Plex . language ) )
elif " imdb " in method : items_found + = check_map ( self . config . IMDb . get_items ( method , value , self . library . Plex . language ) )
elif " tmdb " in method : items_found + = check_map ( self . config . TMDb . get_items ( method , value , self . library . is_movie ) )
elif " tmdb " in method : items_found + = check_map ( self . config . TMDb . get_items ( method , value , self . library . is_movie ) )
elif " trakt " in method : items_found + = check_map ( self . config . Trakt . get_items ( method , value , self . library . is_movie ) )
elif " trakt " in method : items_found + = check_map ( self . config . Trakt . get_items ( method , value , self . library . is_movie ) )
else : logger . error ( " Collection Error: { } method not supported " . format ( method ) )
else : logger . error ( f " Collection Error: { method } method not supported " )
if len ( items ) > 0 : map = self . library . add_to_collection ( collection_obj if collection_obj else collection_name , items , self . filters , self . details [ " show_filtered " ] , map, movie_map , show_map )
if len ( items ) > 0 : rating_key_ map = self . library . add_to_collection ( collection_obj if collection_obj else collection_name , items , self . filters , self . details [ " show_filtered " ] , rating_key_ map, movie_map , show_map )
else : logger . error ( " No items found to add to this collection " )
else : logger . error ( " No items found to add to this collection " )
if len ( missing_movies ) > 0 or len ( missing_shows ) > 0 :
if len ( missing_movies ) > 0 or len ( missing_shows ) > 0 :
logger . info ( " " )
logger . info ( " " )
if len ( missing_movies ) > 0 :
arr_filters = [ ]
not_lang = None
terms = None
for filter_method , filter_data in self . filters :
for filter_method , filter_data in self . filters :
if filter_method . startswith ( " original_language " ) :
if ( filter_method . startswith ( " original_language " ) and self . library . is_movie ) or filter_method . startswith ( " tmdb_vote_count " ) :
terms = util . get_list ( filter_data , lower = True )
arr_filters . append ( ( filter_method , filter_data ) )
not_lang = filter_method . endswith ( " .not " )
if len ( missing_movies ) > 0 :
break
missing_movies_with_names = [ ]
missing_movies_with_names = [ ]
for missing_id in missing_movies :
for missing_id in missing_movies :
try :
try :
movie = self . config . TMDb . get_movie ( missing_id )
movie = self . config . TMDb . get_movie ( missing_id )
title = str ( movie . title )
if not_lang is None or ( not_lang is True and movie . original_language not in terms ) or ( not_lang is False and movie . original_language in terms ) :
missing_movies_with_names . append ( ( title , missing_id ) )
if self . details [ " show_missing " ] is True :
logger . info ( " {} Collection | ? | {} (TMDb: {} ) " . format ( collection_name , title , missing_id ) )
elif self . details [ " show_filtered " ] is True :
logger . info ( " {} Collection | X | {} (TMDb: {} ) " . format ( collection_name , title , missing_id ) )
except Failed as e :
except Failed as e :
logger . error ( e )
logger . error ( e )
logger . info ( " {} Movie {} Missing " . format ( len ( missing_movies_with_names ) , " s " if len ( missing_movies_with_names ) > 1 else " " ) )
continue
match = True
for filter_method , filter_data in arr_filters :
if ( filter_method == " original_language " and movie . original_language not in filter_data ) \
or ( filter_method == " original_language.not " and movie . original_language in filter_data ) \
or ( filter_method == " tmdb_vote_count.gte " and movie . vote_count < filter_data ) \
or ( filter_method == " tmdb_vote_count.lte " and movie . vote_count > filter_data ) :
match = False
break
if match :
missing_movies_with_names . append ( ( movie . title , missing_id ) )
if self . details [ " show_missing " ] is True :
logger . info ( f " { collection_name } Collection | ? | { movie . title } (TMDb: { missing_id } ) " )
elif self . details [ " show_filtered " ] is True :
logger . info ( f " { collection_name } Collection | X | { movie . title } (TMDb: { missing_id } ) " )
logger . info ( f " { len ( missing_movies_with_names ) } Movie { ' s ' if len ( missing_movies_with_names ) > 1 else ' ' } Missing " )
if self . details [ " save_missing " ] is True :
if self . details [ " save_missing " ] is True :
self . library . add_missing ( collection_name , missing_movies_with_names , True )
self . library . add_missing ( collection_name , missing_movies_with_names , True )
if self . do_arr and self . library . Radarr :
if self . do_arr and self . library . Radarr :
self . library . Radarr . add_tmdb ( [ missing_id for title , missing_id in missing_movies_with_names ] , tag = self . details [ " arr_tag " ] )
self . library . Radarr . add_tmdb ( [ missing_id for title , missing_id in missing_movies_with_names ] , tag = self . details [ " arr_tag " ] )
if self . run_again :
self . missing_movies . extend ( [ missing_id for title , missing_id in missing_movies_with_names ] )
if len ( missing_shows ) > 0 and self . library . is_show :
if len ( missing_shows ) > 0 and self . library . is_show :
missing_shows_with_names = [ ]
missing_shows_with_names = [ ]
for missing_id in missing_shows :
for missing_id in missing_shows :
try :
try :
title = str ( self . config . TVDb . get_series ( self . library . Plex . language , tvdb_id = missing_id ) . title . encode ( " ascii " , " replace " ) . decode ( ) )
title = str ( self . config . TVDb . get_series ( self . library . Plex . language , tvdb_id = missing_id ) . title . encode ( " ascii " , " replace " ) . decode ( ) )
missing_shows_with_names . append ( ( title , missing_id ) )
if self . details [ " show_missing " ] is True :
logger . info ( " {} Collection | ? | {} (TVDB: {} ) " . format ( collection_name , title , missing_id ) )
except Failed as e :
except Failed as e :
logger . error ( e )
logger . error ( e )
logger . info ( " {} Show {} Missing " . format ( len ( missing_shows_with_names ) , " s " if len ( missing_shows_with_names ) > 1 else " " ) )
continue
match = True
if arr_filters :
show = self . config . TMDb . get_show ( self . config . TMDb . convert_tvdb_to_tmdb ( missing_id ) )
for filter_method , filter_data in arr_filters :
if ( filter_method == " tmdb_vote_count.gte " and show . vote_count < filter_data ) \
or ( filter_method == " tmdb_vote_count.lte " and show . vote_count > filter_data ) :
match = False
break
if match :
missing_shows_with_names . append ( ( title , missing_id ) )
if self . details [ " show_missing " ] is True :
logger . info ( f " { collection_name } Collection | ? | { title } (TVDB: { missing_id } ) " )
elif self . details [ " show_filtered " ] is True :
logger . info ( f " { collection_name } Collection | X | { title } (TVDb: { missing_id } ) " )
logger . info ( f " { len ( missing_shows_with_names ) } Show { ' s ' if len ( missing_shows_with_names ) > 1 else ' ' } Missing " )
if self . details [ " save_missing " ] is True :
if self . details [ " save_missing " ] is True :
self . library . add_missing ( collection_name , missing_shows_with_names , False )
self . library . add_missing ( collection_name , missing_shows_with_names , False )
if self . do_arr and self . library . Sonarr :
if self . do_arr and self . library . Sonarr :
self . library . Sonarr . add_tvdb ( [ missing_id for title , missing_id in missing_shows_with_names ] , tag = self . details [ " arr_tag " ] )
self . library . Sonarr . add_tvdb ( [ missing_id for title , missing_id in missing_shows_with_names ] , tag = self . details [ " arr_tag " ] )
if self . run_again :
self . missing_shows . extend ( [ missing_id for title , missing_id in missing_shows_with_names ] )
if self . sync and items_found > 0 :
if self . sync and items_found > 0 :
logger . info ( " " )
logger . info ( " " )
count_removed = 0
count_removed = 0
for ratingKey , item in map . items ( ) :
for ratingKey , item in rating_key_ map. items ( ) :
if item is not None :
if item is not None :
logger . info ( " {} Collection | - | {} " . format ( collection_name , item . title ) )
logger . info ( f" { collection_name } Collection | - | { item . title } " )
item . removeCollection ( collection_name )
item . removeCollection ( collection_name )
count_removed + = 1
count_removed + = 1
logger . info ( " {} {} {} Removed " . format ( count_removed , " Movie " if self . library . is_movie else " Show " , " s " if count_removed == 1 else " " ) )
logger . info ( f" { count_removed } { ' Movie ' if self . library . is_movie else ' Show ' } { ' s ' if count_removed == 1 else ' ' } Removed " )
logger . info ( " " )
logger . info ( " " )
def update_details ( self , collection ) :
def update_details ( self , collection ) :
edits = { }
edits = { }
def get_summary ( summary_method , summaries ) :
logger . info ( f " Detail: { summary_method } updated Collection Summary " )
return summaries [ summary_method ]
if " summary " in self . summaries : summary = get_summary ( " summary " , self . summaries )
elif " tmdb_description " in self . summaries : summary = get_summary ( " tmdb_description " , self . summaries )
elif " tmdb_summary " in self . summaries : summary = get_summary ( " tmdb_summary " , self . summaries )
elif " tmdb_biography " in self . summaries : summary = get_summary ( " tmdb_biography " , self . summaries )
elif " tmdb_person " in self . summaries : summary = get_summary ( " tmdb_person " , self . summaries )
elif " tmdb_collection_details " in self . summaries : summary = get_summary ( " tmdb_collection_details " , self . summaries )
elif " tmdb_list_details " in self . summaries : summary = get_summary ( " tmdb_list_details " , self . summaries )
elif " tmdb_actor_details " in self . summaries : summary = get_summary ( " tmdb_actor_details " , self . summaries )
elif " tmdb_crew_details " in self . summaries : summary = get_summary ( " tmdb_crew_details " , self . summaries )
elif " tmdb_director_details " in self . summaries : summary = get_summary ( " tmdb_director_details " , self . summaries )
elif " tmdb_producer_details " in self . summaries : summary = get_summary ( " tmdb_producer_details " , self . summaries )
elif " tmdb_writer_details " in self . summaries : summary = get_summary ( " tmdb_writer_details " , self . summaries )
elif " tmdb_movie_details " in self . summaries : summary = get_summary ( " tmdb_movie_details " , self . summaries )
elif " tmdb_show_details " in self . summaries : summary = get_summary ( " tmdb_show_details " , self . summaries )
else : summary = None
if summary :
edits [ " summary.value " ] = summary
edits [ " summary.locked " ] = 1
if " sort_title " in self . details :
if " sort_title " in self . details :
edits [ " titleSort.value " ] = self . details [ " sort_title " ]
edits [ " titleSort.value " ] = self . details [ " sort_title " ]
edits [ " titleSort.locked " ] = 1
edits [ " titleSort.locked " ] = 1
logger . info ( f " Detail: sort_title updated Collection Sort Title to { self . details [ ' sort_title ' ] } " )
if " content_rating " in self . details :
if " content_rating " in self . details :
edits [ " contentRating.value " ] = self . details [ " content_rating " ]
edits [ " contentRating.value " ] = self . details [ " content_rating " ]
edits [ " contentRating.locked " ] = 1
edits [ " contentRating.locked " ] = 1
if " summary " in self . details :
logger . info ( f " Detail: content_rating updated Collection Content Rating to { self . details [ ' content_rating ' ] } " )
edits [ " summary.value " ] = self . details [ " summary " ]
edits [ " summary.locked " ] = 1
if len ( edits ) > 0 :
logger . debug ( edits )
collection . edit ( * * edits )
collection . reload ( )
logger . info ( " Details: have been updated " )
if " collection_mode " in self . details :
if " collection_mode " in self . details :
collection . modeUpdate ( mode = self . details [ " collection_mode " ] )
collection . modeUpdate ( mode = self . details [ " collection_mode " ] )
logger . info ( f " Detail: collection_mode updated Collection Mode to { self . details [ ' collection_mode ' ] } " )
if " collection_order " in self . details :
if " collection_order " in self . details :
collection . sortUpdate ( sort = self . details [ " collection_order " ] )
collection . sortUpdate ( sort = self . details [ " collection_order " ] )
logger . info ( f " Detail: collection_order updated Collection Order to { self . details [ ' collection_order ' ] } " )
if " label " in self . details :
if " label " in self . details :
item_labels = [ label . tag for label in collection . labels ]
item_labels = [ label . tag for label in collection . labels ]
labels = util . get_list ( self . details [ " label " ] )
labels = util . get_list ( self . details [ " label " ] )
if " label_sync_mode " in self . details and self . details [ " label_sync_mode " ] == " sync " :
if " label_sync_mode " in self . details and self . details [ " label_sync_mode " ] == " sync " :
for label in ( l for l in item_labels if l not in labels ) :
for label in ( l a for l a in item_labels if l a not in labels ) :
collection . removeLabel ( label )
collection . removeLabel ( label )
logger . info ( " Detail: Label { } removed " . format ( label ) )
logger . info ( f " Detail: Label { label } removed " )
for label in ( l for l in labels if l not in item_labels ) :
for label in ( l a for l a in labels if l a not in item_labels ) :
collection . addLabel ( label )
collection . addLabel ( label )
logger . info ( " Detail: Label {} added " . format ( label ) )
logger . info ( f " Detail: Label { label } added " )
if len ( edits ) > 0 :
logger . debug ( edits )
collection . edit ( * * edits )
collection . reload ( )
logger . info ( " Details: have been updated " )
if self . library . asset_directory :
if self . library . asset_directory :
name_mapping = self . name
name_mapping = self . name
@ -708,17 +796,17 @@ class CollectionBuilder:
if self . details [ " name_mapping " ] : name_mapping = self . details [ " name_mapping " ]
if self . details [ " name_mapping " ] : name_mapping = self . details [ " name_mapping " ]
else : logger . error ( " Collection Error: name_mapping attribute is blank " )
else : logger . error ( " Collection Error: name_mapping attribute is blank " )
for ad in self . library . asset_directory :
for ad in self . library . asset_directory :
path = os . path . join ( ad , " {} " . format ( name_mapping ) )
path = os . path . join ( ad , f" { name_mapping } " )
if not os . path . isdir ( path ) :
if not os . path . isdir ( path ) :
continue
continue
matches = glob . glob ( os . path . join ( ad , " {} " . format ( name_mapping ) , " poster.* " ) )
matches = glob . glob ( os . path . join ( ad , f" { name_mapping } " , " poster.* " ) )
if len ( matches ) > 0 :
if len ( matches ) > 0 :
for match in matches :
for match in matches :
self . posters . append ( ( " file " , os . path . abspath ( match ) , " asset_directory " ) )
self . posters [ " asset_directory " ] = os . path . abspath ( match )
matches = glob . glob ( os . path . join ( ad , " {} " . format ( name_mapping ) , " background.* " ) )
matches = glob . glob ( os . path . join ( ad , f" { name_mapping } " , " background.* " ) )
if len ( matches ) > 0 :
if len ( matches ) > 0 :
for match in matches :
for match in matches :
self . backgrounds . append ( ( " file " , os . path . abspath ( match ) , " asset_directory " ) )
self . backgrounds [ " asset_directory " ] = os . path . abspath ( match )
dirs = [ folder for folder in os . listdir ( path ) if os . path . isdir ( os . path . join ( path , folder ) ) ]
dirs = [ folder for folder in os . listdir ( path ) if os . path . isdir ( os . path . join ( path , folder ) ) ]
if len ( dirs ) > 0 :
if len ( dirs ) > 0 :
for item in collection . items ( ) :
for item in collection . items ( ) :
@ -730,25 +818,107 @@ class CollectionBuilder:
background_path = os . path . abspath ( matches [ 0 ] ) if len ( matches ) > 0 else None
background_path = os . path . abspath ( matches [ 0 ] ) if len ( matches ) > 0 else None
if poster_path :
if poster_path :
item . uploadPoster ( filepath = poster_path )
item . uploadPoster ( filepath = poster_path )
logger . info ( " Detail: asset_directory updated { }' s poster to [file] {} " . format ( item . title , poster_path ) )
logger . info ( f " Detail: asset_directory updated { item . title } ' s poster to [file] { poster_path } " )
if background_path :
if background_path :
item . uploadArt ( filepath = background_path )
item . uploadArt ( filepath = background_path )
logger . info ( " Detail: asset_directory updated { }' s background to [file] { }" . format ( item . title , background_path ) )
logger . info ( f " Detail: asset_directory updated { item . title }' s background to [file] { background_path } " )
if poster_path is None and background_path is None :
if poster_path is None and background_path is None :
logger . warning ( " No Files Found: {} " . format ( os . path . join ( path , folder ) ) )
logger . warning ( f " No Files Found: { os . path . join ( path , folder ) } " )
else :
else :
logger . warning ( " No Folder: {} " . format ( os . path . join ( path , folder ) ) )
logger . warning ( f " No Folder: { os . path . join ( path , folder ) } " )
poster = util . choose_from_list ( self . posters , " poster " , list_type = " tuple " )
def set_image ( image_method , images , is_background = False ) :
if not poster and " poster " in self . details : poster = self . details [ " poster " ]
if image_method in [ ' file_poster ' , ' asset_directory ' ] :
if poster :
if is_background : collection . uploadArt ( filepath = images [ image_method ] )
if poster [ 0 ] == " url " : collection . uploadPoster ( url = poster [ 1 ] )
else : collection . uploadPoster ( filepath = images [ image_method ] )
else : collection . uploadPoster ( filepath = poster [ 1 ] )
image_location = " File "
logger . info ( " Detail: {} updated collection poster to [ {} ] {} " . format ( poster [ 2 ] , poster [ 0 ] , poster [ 1 ] ) )
else :
if is_background : collection . uploadArt ( url = images [ image_method ] )
background = util . choose_from_list ( self . backgrounds , " background " , list_type = " tuple " )
else : collection . uploadPoster ( url = images [ image_method ] )
if not background and " background " in self . details : background = self . details [ " background " ]
image_location = " URL "
if background :
logger . info ( f " Detail: { image_method } updated collection { ' background ' if is_background else ' poster ' } to [ { image_location } ] { images [ image_method ] } " )
if background [ 0 ] == " url " : collection . uploadArt ( url = background [ 1 ] )
else : collection . uploadArt ( filepath = background [ 1 ] )
if len ( self . posters ) > 1 :
logger . info ( " Detail: {} updated collection background to [ {} ] {} " . format ( background [ 2 ] , background [ 0 ] , background [ 1 ] ) )
logger . info ( f " { len ( self . posters ) } posters found: " )
for p in self . posters :
logger . info ( f " Method: { p } Poster: { self . posters [ p ] } " )
if " url_poster " in self . posters : set_image ( " url_poster " , self . posters )
elif " file_poster " in self . posters : set_image ( " file_poster " , self . posters )
elif " tmdb_poster " in self . posters : set_image ( " tmdb_poster " , self . posters )
elif " tmdb_profile " in self . posters : set_image ( " tmdb_profile " , self . posters )
elif " asset_directory " in self . posters : set_image ( " asset_directory " , self . posters )
elif " tmdb_person " in self . posters : set_image ( " tmdb_person " , self . posters )
elif " tmdb_collection_details " in self . posters : set_image ( " tmdb_collection " , self . posters )
elif " tmdb_actor_details " in self . posters : set_image ( " tmdb_actor_details " , self . posters )
elif " tmdb_crew_details " in self . posters : set_image ( " tmdb_crew_details " , self . posters )
elif " tmdb_director_details " in self . posters : set_image ( " tmdb_director_details " , self . posters )
elif " tmdb_producer_details " in self . posters : set_image ( " tmdb_producer_details " , self . posters )
elif " tmdb_writer_details " in self . posters : set_image ( " tmdb_writer_details " , self . posters )
elif " tmdb_movie_details " in self . posters : set_image ( " tmdb_movie " , self . posters )
elif " tmdb_show_details " in self . posters : set_image ( " tmdb_show " , self . posters )
else : logger . info ( " No poster to update " )
logger . info ( " " )
if len ( self . backgrounds ) > 1 :
logger . info ( f " { len ( self . backgrounds ) } backgrounds found: " )
for b in self . backgrounds :
logger . info ( f " Method: { b } Background: { self . backgrounds [ b ] } " )
if " url_background " in self . backgrounds : set_image ( " url_background " , self . backgrounds , is_background = True )
elif " file_background " in self . backgrounds : set_image ( " file_poster " , self . backgrounds , is_background = True )
elif " tmdb_background " in self . backgrounds : set_image ( " tmdb_poster " , self . backgrounds , is_background = True )
elif " asset_directory " in self . backgrounds : set_image ( " asset_directory " , self . backgrounds , is_background = True )
elif " tmdb_collection_details " in self . backgrounds : set_image ( " tmdb_collection " , self . backgrounds , is_background = True )
elif " tmdb_movie_details " in self . backgrounds : set_image ( " tmdb_movie " , self . backgrounds , is_background = True )
elif " tmdb_show_details " in self . backgrounds : set_image ( " tmdb_show " , self . backgrounds , is_background = True )
else : logger . info ( " No background to update " )
def run_collections_again ( self , library , collection_obj , movie_map , show_map ) :
collection_items = collection_obj . items ( ) if isinstance ( collection_obj , Collections ) else [ ]
name = collection_obj . title if isinstance ( collection_obj , Collections ) else collection_obj
rating_keys = [ movie_map [ mm ] for mm in self . missing_movies if mm in movie_map ]
if library . is_show :
rating_keys . extend ( [ show_map [ sm ] for sm in self . missing_shows if sm in show_map ] )
if len ( rating_keys ) > 0 :
for rating_key in rating_keys :
try :
current = library . fetchItem ( int ( rating_key ) )
except ( BadRequest , NotFound ) :
logger . error ( f " Plex Error: Item { rating_key } not found " )
continue
if current in collection_items :
logger . info ( f " { name } Collection | = | { current . title } " )
else :
current . addCollection ( name )
logger . info ( f " { name } Collection | + | { current . title } " )
logger . info ( f " { len ( rating_keys ) } { ' Movie ' if library . is_movie else ' Show ' } { ' s ' if len ( rating_keys ) > 1 else ' ' } Processed " )
if len ( self . missing_movies ) > 0 :
logger . info ( " " )
for missing_id in self . missing_movies :
if missing_id not in movie_map :
try :
movie = self . config . TMDb . get_movie ( missing_id )
except Failed as e :
logger . error ( e )
continue
if self . details [ " show_missing " ] is True :
logger . info ( f " { name } Collection | ? | { movie . title } (TMDb: { missing_id } ) " )
logger . info ( " " )
logger . info ( f " { len ( self . missing_movies ) } Movie { ' s ' if len ( self . missing_movies ) > 1 else ' ' } Missing " )
if len ( self . missing_shows ) > 0 and library . is_show :
logger . info ( " " )
for missing_id in self . missing_shows :
if missing_id not in show_map :
try :
title = str ( self . config . TVDb . get_series ( self . library . Plex . language , tvdb_id = missing_id ) . title . encode ( " ascii " , " replace " ) . decode ( ) )
except Failed as e :
logger . error ( e )
continue
if self . details [ " show_missing " ] is True :
logger . info ( f " { name } Collection | ? | { title } (TVDb: { missing_id } ) " )
logger . info ( f " { len ( self . missing_shows ) } Show { ' s ' if len ( self . missing_shows ) > 1 else ' ' } Missing " )