@ -76,13 +76,13 @@ summary_details = [
]
]
poster_details = [ " url_poster " , " tmdb_poster " , " tmdb_profile " , " tvdb_poster " , " file_poster " ]
poster_details = [ " url_poster " , " tmdb_poster " , " tmdb_profile " , " tvdb_poster " , " file_poster " ]
background_details = [ " url_background " , " tmdb_background " , " tvdb_background " , " file_background " ]
background_details = [ " url_background " , " tmdb_background " , " tvdb_background " , " file_background " ]
boolean_details = [ " visible_library " , " visible_home " , " visible_shared " , " show_filtered " , " show_missing " , " save_missing " , " item_assets " , " missing_only_released " , " revert_overlay " ]
boolean_details = [ " visible_library " , " visible_home " , " visible_shared " , " show_filtered " , " show_missing " , " save_missing " , " item_assets " , " missing_only_released " , " revert_overlay " , " delete_below_minimum " ]
string_details = [ " sort_title " , " content_rating " , " name_mapping " ]
string_details = [ " sort_title " , " content_rating " , " name_mapping " ]
ignored_details = [
ignored_details = [
" smart_filter " , " smart_label " , " smart_url " , " run_again " , " schedule " , " sync_mode " , " template " , " test " ,
" smart_filter " , " smart_label " , " smart_url " , " run_again " , " schedule " , " sync_mode " , " template " , " test " ,
" tmdb_person " , " build_collection " , " collection_order " , " collection_level " , " validate_builders "
" tmdb_person " , " build_collection " , " collection_order " , " collection_level " , " validate_builders " , " collection_name "
]
]
details = [ " collection_mode " , " collection_order " , " collection_level " , " label" ] + boolean_details + string_details
details = [ " collection_mode " , " collection_order " , " collection_level " , " collection_minimum" , " label" ] + boolean_details + string_details
collectionless_details = [ " collection_order " , " plex_collectionless " , " label " , " label_sync_mode " , " test " ] + \
collectionless_details = [ " collection_order " , " plex_collectionless " , " label " , " label_sync_mode " , " test " ] + \
poster_details + background_details + summary_details + string_details
poster_details + background_details + summary_details + string_details
item_details = [ " item_label " , " item_radarr_tag " , " item_sonarr_tag " , " item_overlay " ] + list ( plex . item_advance_keys . keys ( ) )
item_details = [ " item_label " , " item_radarr_tag " , " item_sonarr_tag " , " item_overlay " ] + list ( plex . item_advance_keys . keys ( ) )
@ -94,12 +94,12 @@ sonarr_details = [
all_filters = [
all_filters = [
" actor " , " actor.not " ,
" actor " , " actor.not " ,
" audio_language " , " audio_language.not " ,
" audio_language " , " audio_language.not " ,
" audio_track_title " , " audio_track_title.not " , " audio_track_title. begins" , " audio_track_title.ends " , " audio_track_title.regex " ,
" audio_track_title " , " audio_track_title.not " , " audio_track_title. is" , " audio_track_title.isnot " , " audio_track_title. begins" , " audio_track_title.ends " , " audio_track_title.regex " ,
" collection " , " collection.not " ,
" collection " , " collection.not " ,
" content_rating " , " content_rating.not " ,
" content_rating " , " content_rating.not " ,
" country " , " country.not " ,
" country " , " country.not " ,
" director " , " director.not " ,
" director " , " director.not " ,
" filepath " , " filepath.not " , " filepath. begins" , " filepath.ends " , " filepath.regex " ,
" filepath " , " filepath.not " , " filepath. is" , " filepath.isnot " , " filepath. begins" , " filepath.ends " , " filepath.regex " ,
" genre " , " genre.not " ,
" genre " , " genre.not " ,
" label " , " label.not " ,
" label " , " label.not " ,
" producer " , " producer.not " ,
" producer " , " producer.not " ,
@ -108,7 +108,7 @@ all_filters = [
" last_played " , " last_played.not " , " last_played.before " , " last_played.after " , " last_played.regex " ,
" last_played " , " last_played.not " , " last_played.before " , " last_played.after " , " last_played.regex " ,
" first_episode_aired " , " first_episode_aired.not " , " first_episode_aired.before " , " first_episode_aired.after " , " first_episode_aired.regex " ,
" first_episode_aired " , " first_episode_aired.not " , " first_episode_aired.before " , " first_episode_aired.after " , " first_episode_aired.regex " ,
" last_episode_aired " , " last_episode_aired.not " , " last_episode_aired.before " , " last_episode_aired.after " , " last_episode_aired.regex " ,
" last_episode_aired " , " last_episode_aired.not " , " last_episode_aired.before " , " last_episode_aired.after " , " last_episode_aired.regex " ,
" title " , " title.not " , " title. begins" , " title.ends " , " title.regex " ,
" title " , " title.not " , " title. is" , " title.isnot " , " title. begins" , " title.ends " , " title.regex " ,
" plays.gt " , " plays.gte " , " plays.lt " , " plays.lte " ,
" plays.gt " , " plays.gte " , " plays.lt " , " plays.lte " ,
" tmdb_vote_count.gt " , " tmdb_vote_count.gte " , " tmdb_vote_count.lt " , " tmdb_vote_count.lte " ,
" tmdb_vote_count.gt " , " tmdb_vote_count.gte " , " tmdb_vote_count.lt " , " tmdb_vote_count.lte " ,
" duration.gt " , " duration.gte " , " duration.lt " , " duration.lte " ,
" duration.gt " , " duration.gte " , " duration.lt " , " duration.lte " ,
@ -116,7 +116,7 @@ all_filters = [
" user_rating.gt " , " user_rating.gte " , " user_rating.lt " , " user_rating.lte " ,
" user_rating.gt " , " user_rating.gte " , " user_rating.lt " , " user_rating.lte " ,
" audience_rating.gt " , " audience_rating.gte " , " audience_rating.lt " , " audience_rating.lte " ,
" audience_rating.gt " , " audience_rating.gte " , " audience_rating.lt " , " audience_rating.lte " ,
" critic_rating.gt " , " critic_rating.gte " , " critic_rating.lt " , " critic_rating.lte " ,
" critic_rating.gt " , " critic_rating.gte " , " critic_rating.lt " , " critic_rating.lte " ,
" studio " , " studio.not " , " studio. begins" , " studio.ends " , " studio.regex " ,
" studio " , " studio.not " , " studio. is" , " studio.isnot " , " studio. begins" , " studio.ends " , " studio.regex " ,
" subtitle_language " , " subtitle_language.not " ,
" subtitle_language " , " subtitle_language.not " ,
" resolution " , " resolution.not " ,
" resolution " , " resolution.not " ,
" writer " , " writer.not " ,
" writer " , " writer.not " ,
@ -126,7 +126,7 @@ all_filters = [
tmdb_filters = [ " original_language " , " tmdb_vote_count " , " tmdb_year " , " first_episode_aired " , " last_episode_aired " ]
tmdb_filters = [ " original_language " , " tmdb_vote_count " , " tmdb_year " , " first_episode_aired " , " last_episode_aired " ]
movie_only_filters = [
movie_only_filters = [
" audio_language " , " audio_language.not " ,
" audio_language " , " audio_language.not " ,
" audio_track_title " , " audio_track_title.not " , " audio_track_title. begins" , " audio_track_title.ends " , " audio_track_title.regex " ,
" audio_track_title " , " audio_track_title.not " , " audio_track_title. is" , " audio_track_title.isnot " , " audio_track_title. begins" , " audio_track_title.ends " , " audio_track_title.regex " ,
" country " , " country.not " ,
" country " , " country.not " ,
" director " , " director.not " ,
" director " , " director.not " ,
" duration.gt " , " duration.gte " , " duration.lt " , " duration.lte " ,
" duration.gt " , " duration.gte " , " duration.lt " , " duration.lte " ,
@ -158,7 +158,7 @@ class CollectionBuilder:
self . config = config
self . config = config
self . library = library
self . library = library
self . metadata = metadata
self . metadata = metadata
self . name = name
self . mapping_ name = name
self . no_missing = no_missing
self . no_missing = no_missing
self . data = data
self . data = data
self . language = self . library . Plex . language
self . language = self . library . Plex . language
@ -187,11 +187,23 @@ class CollectionBuilder:
self . backgrounds = { }
self . backgrounds = { }
self . summaries = { }
self . summaries = { }
self . schedule = " "
self . schedule = " "
self . minimum = self . library . collection_minimum
self . delete_below_minimum = self . library . delete_below_minimum
self . current_time = datetime . now ( )
self . current_time = datetime . now ( )
self . current_year = self . current_time . year
self . current_year = self . current_time . year
methods = { m . lower ( ) : m for m in self . data }
methods = { m . lower ( ) : m for m in self . data }
if " collection_name " in methods :
logger . debug ( " " )
logger . debug ( " Validating Method: collection_name " )
if not self . data [ methods [ " collection_name " ] ] :
raise Failed ( " Collection Error: collection_name attribute is blank " )
logger . debug ( f " Value: { self . data [ methods [ ' collection_name ' ] ] } " )
self . name = self . data [ methods [ " collection_name " ] ]
else :
self . name = self . mapping_name
if " template " in methods :
if " template " in methods :
logger . debug ( " " )
logger . debug ( " " )
logger . debug ( " Validating Method: template " )
logger . debug ( " Validating Method: template " )
@ -669,6 +681,8 @@ class CollectionBuilder:
self . details [ method_name ] = plex . collection_mode_options [ str ( method_data ) . lower ( ) ]
self . details [ method_name ] = plex . collection_mode_options [ str ( method_data ) . lower ( ) ]
else :
else :
raise Failed ( f " Collection Error: { method_data } 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) " )
raise Failed ( f " Collection Error: { method_data } 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_minimum " :
self . minimum = util . parse ( method_name , method_data , datatype = " int " , minimum = 1 )
elif method_name == " label " :
elif method_name == " label " :
if " label " in methods and " label.sync " in methods :
if " label " in methods and " label.sync " in methods :
raise Failed ( " Collection Error: Cannot use label and label.sync together " )
raise Failed ( " Collection Error: Cannot use label and label.sync together " )
@ -774,7 +788,7 @@ class CollectionBuilder:
elif not dict_data [ dict_methods [ " tag " ] ] :
elif not dict_data [ dict_methods [ " tag " ] ] :
raise Failed ( " Collection Error: anidb_tag tag attribute is blank " )
raise Failed ( " Collection Error: anidb_tag tag attribute is blank " )
else :
else :
new_dictionary [ " tag " ] = util . regex_first_int ( dict_data [ dict_methods [ " username " ] ] , " AniDB Tag ID " )
new_dictionary [ " tag " ] = util . regex_first_int ( dict_data [ dict_methods [ " tag " ] ] , " AniDB Tag ID " )
new_dictionary [ " limit " ] = util . parse ( " limit " , dict_data , datatype = " int " , methods = dict_methods , default = 0 , parent = method_name , minimum = 0 )
new_dictionary [ " limit " ] = util . parse ( " limit " , dict_data , datatype = " int " , methods = dict_methods , default = 0 , parent = method_name , minimum = 0 )
self . builders . append ( ( method_name , new_dictionary ) )
self . builders . append ( ( method_name , new_dictionary ) )
@ -782,7 +796,7 @@ class CollectionBuilder:
if method_name in [ " anilist_id " , " anilist_relations " , " anilist_studio " ] :
if method_name in [ " anilist_id " , " anilist_relations " , " anilist_studio " ] :
for anilist_id in self . config . AniList . validate_anilist_ids ( method_data , studio = method_name == " anilist_studio " ) :
for anilist_id in self . config . AniList . validate_anilist_ids ( method_data , studio = method_name == " anilist_studio " ) :
self . builders . append ( ( method_name , anilist_id ) )
self . builders . append ( ( method_name , anilist_id ) )
elif method_name in [ " anilist_popular " , " anilist_t op_rated" ] :
elif method_name in [ " anilist_popular " , " anilist_t rending" , " anilist_t op_rated" ] :
self . builders . append ( ( method_name , util . parse ( method_name , method_data , datatype = " int " , default = 10 ) ) )
self . builders . append ( ( method_name , util . parse ( method_name , method_data , datatype = " int " , default = 10 ) ) )
elif method_name == " anilist_search " :
elif method_name == " anilist_search " :
if self . current_time . month in [ 12 , 1 , 2 ] : current_season = " winter "
if self . current_time . month in [ 12 , 1 , 2 ] : current_season = " winter "
@ -802,13 +816,14 @@ class CollectionBuilder:
new_dictionary [ " year " ] = self . current_year
new_dictionary [ " year " ] = self . current_year
elif search_attr == " year " :
elif search_attr == " year " :
new_dictionary [ search_attr ] = util . parse ( search_attr , search_data , datatype = " int " , parent = method_name , default = self . current_year , minimum = 1917 , maximum = self . current_year + 1 )
new_dictionary [ search_attr ] = util . parse ( search_attr , search_data , datatype = " int " , parent = method_name , default = self . current_year , minimum = 1917 , maximum = self . current_year + 1 )
if " season " not in dict_methods :
logger . warning ( f " Collection Warning: { method_name } season attribute not found using this season: { current_season } by default " )
new_dictionary [ " season " ] = current_season
elif search_data is None :
elif search_data is None :
raise Failed ( f " Collection Error: { method_name } { search_final } attribute is blank " )
raise Failed ( f " Collection Error: { method_name } { search_final } attribute is blank " )
elif search_attr == " adult " :
elif search_attr == " adult " :
new_dictionary [ search_attr ] = util . parse ( search_attr , search_data , datatype = " bool " , parent = method_name )
new_dictionary [ search_attr ] = util . parse ( search_attr , search_data , datatype = " bool " , parent = method_name )
elif search_attr == " country " :
new_dictionary [ search_attr ] = util . parse ( search_attr , search_data , options = anilist . country_codes , parent = method_name )
elif search_attr == " source " :
new_dictionary [ search_attr ] = util . parse ( search_attr , search_data , options = anilist . media_source , parent = method_name )
elif search_attr in [ " episodes " , " duration " , " score " , " popularity " ] :
elif search_attr in [ " episodes " , " duration " , " score " , " popularity " ] :
new_dictionary [ search_final ] = util . parse ( search_final , search_data , datatype = " int " , parent = method_name )
new_dictionary [ search_final ] = util . parse ( search_final , search_data , datatype = " int " , parent = method_name )
elif search_attr in [ " format " , " status " , " genre " , " tag " , " tag_category " ] :
elif search_attr in [ " format " , " status " , " genre " , " tag " , " tag_category " ] :
@ -823,7 +838,7 @@ class CollectionBuilder:
raise Failed ( f " Collection Error: { method_name } { search_final } attribute not supported " )
raise Failed ( f " Collection Error: { method_name } { search_final } attribute not supported " )
if len ( new_dictionary ) == 0 :
if len ( new_dictionary ) == 0 :
raise Failed ( f " Collection Error: { method_name } must have at least one valid search option " )
raise Failed ( f " Collection Error: { method_name } must have at least one valid search option " )
new_dictionary [ " sort_by " ] = util . parse ( " sort_by " , dict_data , methods = dict_methods , parent = method_name , default = " score " , options = [ " score " , " popular " ] )
new_dictionary [ " sort_by " ] = util . parse ( " sort_by " , dict_data , methods = dict_methods , parent = method_name , default = " score " , options = anilist . sort_options )
new_dictionary [ " limit " ] = util . parse ( " limit " , dict_data , datatype = " int " , methods = dict_methods , default = 0 , parent = method_name )
new_dictionary [ " limit " ] = util . parse ( " limit " , dict_data , datatype = " int " , methods = dict_methods , default = 0 , parent = method_name )
self . builders . append ( ( method_name , new_dictionary ) )
self . builders . append ( ( method_name , new_dictionary ) )
@ -1068,13 +1083,13 @@ class CollectionBuilder:
rating_keys = self . library . Tautulli . get_rating_keys ( self . library , value )
rating_keys = self . library . Tautulli . get_rating_keys ( self . library , value )
elif " anidb " in method :
elif " anidb " in method :
anidb_ids = self . config . AniDB . get_anidb_ids ( method , value , self . language )
anidb_ids = self . config . AniDB . get_anidb_ids ( method , value , self . language )
ids = self . config . Convert . anidb_to_ids ( anidb_ids )
ids = self . config . Convert . anidb_to_ids ( anidb_ids , self . library )
elif " anilist " in method :
elif " anilist " in method :
anilist_ids = self . config . AniList . get_anilist_ids ( method , value )
anilist_ids = self . config . AniList . get_anilist_ids ( method , value )
ids = self . config . Convert . anilist_to_ids ( anilist_ids )
ids = self . config . Convert . anilist_to_ids ( anilist_ids , self . library )
elif " mal " in method :
elif " mal " in method :
mal_ids = self . config . MyAnimeList . get_mal_ids ( method , value )
mal_ids = self . config . MyAnimeList . get_mal_ids ( method , value )
ids = self . config . Convert . myanimelist_to_ids ( mal_ids )
ids = self . config . Convert . myanimelist_to_ids ( mal_ids , self . library )
elif " tvdb " in method :
elif " tvdb " in method :
ids = self . config . TVDb . get_tvdb_ids ( method , value , self . language )
ids = self . config . TVDb . get_tvdb_ids ( method , value , self . language )
elif " imdb " in method :
elif " imdb " in method :
@ -1100,7 +1115,9 @@ class CollectionBuilder:
for i , input_data in enumerate ( ids , 1 ) :
for i , input_data in enumerate ( ids , 1 ) :
input_id , id_type = input_data
input_id , id_type = input_data
util . print_return ( f " Parsing ID { i } / { total_ids } " )
util . print_return ( f " Parsing ID { i } / { total_ids } " )
if id_type == " tmdb " and not self . parts_collection :
if id_type == " ratingKey " :
rating_keys . append ( input_id )
elif id_type == " tmdb " and not self . parts_collection :
if input_id in self . library . movie_map :
if input_id in self . library . movie_map :
rating_keys . append ( self . library . movie_map [ input_id ] [ 0 ] )
rating_keys . append ( self . library . movie_map [ input_id ] [ 0 ] )
elif input_id not in self . missing_movies :
elif input_id not in self . missing_movies :
@ -1122,12 +1139,12 @@ class CollectionBuilder:
else :
else :
if self . do_missing :
if self . do_missing :
try :
try :
tmdb_id , tmdb_type = self . config . Convert . imdb_to_tmdb ( input_id )
tmdb_id , tmdb_type = self . config . Convert . imdb_to_tmdb ( input_id , fail = True )
if tmdb_type == " movie " :
if tmdb_type == " movie " :
if tmdb_id not in self . missing_movies :
if tmdb_id not in self . missing_movies :
self . missing_movies . append ( tmdb_id )
self . missing_movies . append ( tmdb_id )
else :
else :
tvdb_id = self . config . Convert . tmdb_to_tvdb ( tmdb_id )
tvdb_id = self . config . Convert . tmdb_to_tvdb ( tmdb_id , fail = True )
if tvdb_id not in self . missing_shows :
if tvdb_id not in self . missing_shows :
self . missing_shows . append ( tvdb_id )
self . missing_shows . append ( tvdb_id )
except Failed as e :
except Failed as e :
@ -1135,26 +1152,28 @@ class CollectionBuilder:
continue
continue
elif id_type == " tvdb_season " and self . collection_level == " season " :
elif id_type == " tvdb_season " and self . collection_level == " season " :
show_id , season_num = input_id . split ( " _ " )
show_id , season_num = input_id . split ( " _ " )
if int ( show_id ) in self . library . show_map :
show_id = int ( show_id )
show_item = self . library . fetchItem ( self . library . show_map [ int ( show_id ) ] [ 0 ] )
if show_id in self . library . show_map :
show_item = self . library . fetchItem ( self . library . show_map [ show_id ] [ 0 ] )
try :
try :
episode_item = show_item . season ( season = int ( season_num ) )
episode_item = show_item . season ( season = int ( season_num ) )
rating_keys . append ( episode_item . ratingKey )
rating_keys . append ( episode_item . ratingKey )
except NotFound :
except NotFound :
self . missing_parts . append ( f " { show_item . title } Season: { season_num } Missing " )
self . missing_parts . append ( f " { show_item . title } Season: { season_num } Missing " )
elif int ( show_id ) not in self . missing_shows :
elif show_id not in self . missing_shows :
self . missing_shows . append ( int ( show_id ) )
self . missing_shows . append ( show_id )
elif id_type == " tvdb_episode " and self . collection_level == " episode " :
elif id_type == " tvdb_episode " and self . collection_level == " episode " :
show_id , season_num , episode_num = input_id . split ( " _ " )
show_id , season_num , episode_num = input_id . split ( " _ " )
if int ( show_id ) in self . library . show_map :
show_id = int ( show_id )
show_item = self . library . fetchItem ( self . library . show_map [ int ( show_id ) ] [ 0 ] )
if show_id in self . library . show_map :
show_item = self . library . fetchItem ( self . library . show_map [ show_id ] [ 0 ] )
try :
try :
episode_item = show_item . episode ( season = int ( season_num ) , episode = int ( episode_num ) )
episode_item = show_item . episode ( season = int ( season_num ) , episode = int ( episode_num ) )
rating_keys . append ( episode_item . ratingKey )
rating_keys . append ( episode_item . ratingKey )
except NotFound :
except NotFound :
self . missing_parts . append ( f " { show_item . title } Season: { season_num } Episode: { episode_num } Missing " )
self . missing_parts . append ( f " { show_item . title } Season: { season_num } Episode: { episode_num } Missing " )
elif int ( show_id ) not in self . missing_shows :
elif show_id not in self . missing_shows :
self . missing_shows . append ( int ( show_id ) )
self . missing_shows . append ( show_id )
util . print_end ( )
util . print_end ( )
if len ( rating_keys ) > 0 :
if len ( rating_keys ) > 0 :
@ -1364,7 +1383,7 @@ class CollectionBuilder:
else :
else :
logger . error ( err )
logger . error ( err )
return valid_regex
return valid_regex
elif attribute in [ " title " , " studio " , " episode_title " , " audio_track_title " ] and modifier in [ " " , " .not " , " . begins" , " .ends " ] :
elif attribute in [ " title " , " studio " , " episode_title " , " audio_track_title " ] and modifier in [ " " , " .not " , " . is" , " .isnot " , " . begins" , " .ends " ] :
return smart_pair ( util . get_list ( data , split = False ) )
return smart_pair ( util . get_list ( data , split = False ) )
elif attribute == " original_language " :
elif attribute == " original_language " :
return util . get_list ( data , lower = True )
return util . get_list ( data , lower = True )
@ -1800,6 +1819,10 @@ class CollectionBuilder:
os . remove ( og_image )
os . remove ( og_image )
self . config . Cache . update_image_map ( item . ratingKey , self . library . image_table_name , " " , " " )
self . config . Cache . update_image_map ( item . ratingKey , self . library . image_table_name , " " , " " )
def delete_collection ( self ) :
if self . obj :
self . library . query ( self . obj . delete )
def update_details ( self ) :
def update_details ( self ) :
if not self . obj and self . smart_url :
if not self . obj and self . smart_url :
self . library . create_smart_collection ( self . name , self . smart_type_key , self . smart_url )
self . library . create_smart_collection ( self . name , self . smart_type_key , self . smart_url )