@ -7,13 +7,17 @@ try:
from modules . config import Config
from modules . util import Failed
except ModuleNotFoundError :
print ( " Error: Requirements are not installed " )
print ( " Requirements Error: Requirements are not installed " )
sys . exit ( 0 )
if sys . version_info [ 0 ] != 3 or sys . version_info [ 1 ] < 6 :
print ( " Version Error: Version: %s . %s . %s incompatible please use Python 3.6+ " % ( sys . version_info [ 0 ] , sys . version_info [ 1 ] , sys . version_info [ 2 ] ) )
sys . exit ( 0 )
parser = argparse . ArgumentParser ( )
parser . add_argument ( " -db " , " --debug " , dest = " debug " , help = argparse . SUPPRESS , action = " store_true " , default = False )
parser . add_argument ( " -c " , " --config " , dest = " config " , help = " Run with desired *.yml file " , type = str )
parser . add_argument ( " -t " , " --time " , dest = " time " , help = " Time to update each day use format HH:MM (Default: 03:00)" , default = " 03:00 " , type = str )
parser . add_argument ( " -t " , " --time " , dest = " time " , help = " Time s to update each day use format HH:MM (Default: 03:00) (comma-separated list )" , default = " 03:00 " , type = str )
parser . add_argument ( " -re " , " --resume " , dest = " resume " , help = " Resume collection run from a specific collection " , type = str )
parser . add_argument ( " -r " , " --run " , dest = " run " , help = " Run without the scheduler " , action = " store_true " , default = False )
parser . add_argument ( " -rt " , " --test " , " --tests " , " --run-test " , " --run-tests " , dest = " test " , help = " Run in debug mode with only collections that have test: true " , action = " store_true " , default = False )
@ -48,9 +52,10 @@ collections = os.environ.get("PMM_COLLECTIONS") if os.environ.get("PMM_COLLECTIO
libraries = os . environ . get ( " PMM_LIBRARIES " ) if os . environ . get ( " PMM_LIBRARIES " ) else args . libraries
resume = os . environ . get ( " PMM_RESUME " ) if os . environ . get ( " PMM_RESUME " ) else args . resume
time_to_run = os . environ . get ( " PMM_TIME " ) if os . environ . get ( " PMM_TIME " ) else args . time
if not re . match ( " ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$ " , time_to_run ) :
raise util . Failed ( f " Argument Error: time argument invalid: { time_to_run } must be in the HH:MM format " )
times_to_run = util . get_list ( os . environ . get ( " PMM_TIME " ) if os . environ . get ( " PMM_TIME " ) else args . time )
for time_to_run in times_to_run :
if not re . match ( " ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$ " , time_to_run ) :
raise util . Failed ( f " Argument Error: time argument invalid: { time_to_run } must be in the HH:MM format " )
util . separating_character = os . environ . get ( " PMM_DIVIDER " ) [ 0 ] if os . environ . get ( " PMM_DIVIDER " ) else args . divider [ 0 ]
@ -83,7 +88,7 @@ logger.addHandler(cmd_handler)
sys . excepthook = util . my_except_hook
def start ( config_path , is_test , daily , requested_collections , requested_libraries , resume_from ) :
def start ( config_path , is_test = False , time_scheduled = None , requested_collections = None , requested_libraries = None , resume_from = None ) :
file_logger = os . path . join ( default_dir , " logs " , " meta.log " )
should_roll_over = os . path . isfile ( file_logger )
file_handler = logging . handlers . RotatingFileHandler ( file_logger , delay = True , mode = " w " , backupCount = 10 , encoding = " utf-8 " )
@ -101,16 +106,20 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie
logger . info ( util . centered ( " |_| |_| \\ ___/_/ \\ _ \\ |_| |_| \\ ___| \\ __ \\ __,_| |_| |_| \\ __,_|_| |_| \\ __,_| \\ __, | \\ ___|_| " ) )
logger . info ( util . centered ( " |___/ " ) )
logger . info ( util . centered ( " Version: 1.9.3-beta1 " ) )
if daily: start_type = " Daily "
if time_scheduled: start_type = f " { time_scheduled } "
elif is_test : start_type = " Test "
elif requested_collections : start_type = " Collections "
elif requested_libraries : start_type = " Libraries "
else : start_type = " "
start_time = datetime . now ( )
if time_scheduled is None :
time_scheduled = start_time . strftime ( " % H: % M " )
util . separator ( f " Starting { start_type } Run " )
try :
config = Config ( default_dir , config_path , requested_libraries )
update_libraries ( config , is_test , requested_collections , resume_from )
config = Config ( default_dir , config_path = config_path , is_test = is_test ,
time_scheduled = time_scheduled , requested_collections = requested_collections ,
requested_libraries = requested_libraries , resume_from = resume_from )
update_libraries ( config )
except Exception as e :
util . print_stacktrace ( )
logger . critical ( e )
@ -118,7 +127,7 @@ def start(config_path, is_test, daily, requested_collections, requested_librarie
util . separator ( f " Finished { start_type } Run \n Run Time: { str ( datetime . now ( ) - start_time ) . split ( ' . ' ) [ 0 ] } " )
logger . removeHandler ( file_handler )
def update_libraries ( config , is_test , requested_collections , resume_from ):
def update_libraries ( config ):
for library in config . libraries :
os . makedirs ( os . path . join ( default_dir , " logs " , library . mapping_name , " collections " ) , exist_ok = True )
col_file_logger = os . path . join ( default_dir , " logs " , library . mapping_name , " library.log " )
@ -136,29 +145,29 @@ def update_libraries(config, is_test, requested_collections, resume_from):
util . separator ( f " Mapping { library . name } Library " , space = False , border = False )
logger . info ( " " )
movie_map , show_map = map_guids ( config , library )
if not is_test and not resume_from and not collection_only and library . mass_update :
if not config. test_mode and not config . resume_from and not collection_only and library . mass_update :
mass_metadata ( config , library , movie_map , show_map )
for metadata in library . metadata_files :
logger . info ( " " )
util . separator ( f " Running Metadata File \n { metadata . path } " )
if not is_test and not resume_from and not collection_only :
if not config. test_mode and not config . resume_from and not collection_only :
try :
metadata . update_metadata ( config . TMDb , is_test )
metadata . update_metadata ( config . TMDb , config. test_mode )
except Failed as e :
logger . error ( e )
collections_to_run = metadata . get_collections ( requested_collections)
if resume_from and resume_from not in collections_to_run :
collections_to_run = metadata . get_collections ( config. requested_collections)
if config. resume_from and config . resume_from not in collections_to_run :
logger . info ( " " )
logger . warning ( f " Collection: { resume_from} not in Metadata File: { metadata . path } " )
logger . warning ( f " Collection: { config. resume_from} not in Metadata File: { metadata . path } " )
continue
if collections_to_run and not library_only :
logger . info ( " " )
util . separator ( f " { ' Test ' if is_test else ' ' } Collections " )
util . separator ( f " { ' Test ' if config. test_mode else ' ' } Collections " )
logger . removeHandler ( library_handler )
r esume_from = r un_collection( config , library , metadata , collections_to_run , is_test , resume_from , movie_map , show_map )
r un_collection( config , library , metadata , movie_map , show_map )
logger . addHandler ( library_handler )
if not is_test and not requested_collections and ( ( library . show_unmanaged and not library_only ) or ( library . assets_for_all and not collection_only ) ) :
if not config. test_mode and not config . requested_collections and ( ( library . show_unmanaged and not library_only ) or ( library . assets_for_all and not collection_only ) ) :
logger . info ( " " )
util . separator ( f " Other { library . name } Library Operations " )
unmanaged_collections = [ ]
@ -240,9 +249,11 @@ def map_guids(config, library):
movie_map = { }
show_map = { }
length = 0
logger . info ( f " Mapp ing { ' Movie ' if library . is_movie else ' Show ' } Library: { library . name } " )
logger . info ( f " Load ing { ' Movie ' if library . is_movie else ' Show ' } Library: { library . name } " )
logger . info ( " " )
items = library . Plex . all ( )
logger . info ( f " Mapping { ' Movie ' if library . is_movie else ' Show ' } Library: { library . name } " )
logger . info ( " " )
for i , item in enumerate ( items , 1 ) :
length = util . print_return ( length , f " Processing: { i } / { len ( items ) } { item . title } " )
id_type , main_id = config . Convert . get_id ( item , library , length )
@ -379,11 +390,11 @@ def mass_metadata(config, library, movie_map, show_map):
except Failed as e :
logger . error ( e )
def run_collection ( config , library , metadata , requested_collections, is_test , resume_from , movie_map, show_map ) :
def run_collection ( config , library , metadata , movie_map, show_map ) :
logger . info ( " " )
for mapping_name , collection_attrs in requested_collections. items ( ) :
for mapping_name , collection_attrs in config. requested_collections. items ( ) :
collection_start = datetime . now ( )
if is_test and ( " test " not in collection_attrs or collection_attrs [ " test " ] is not True ) :
if config. test_mode and ( " test " not in collection_attrs or collection_attrs [ " test " ] is not True ) :
no_template_test = True
if " template " in collection_attrs and collection_attrs [ " template " ] :
for data_template in util . get_list ( collection_attrs [ " template " ] , split = False ) :
@ -398,10 +409,10 @@ def run_collection(config, library, metadata, requested_collections, is_test, re
if no_template_test :
continue
if resume_from and resume_from != mapping_name :
if config. resume_from and config . resume_from != mapping_name :
continue
elif resume_from == mapping_name :
resume_from = None
elif config. resume_from == mapping_name :
config. resume_from = None
logger . info ( " " )
util . separator ( f " Resuming Collections " )
@ -481,27 +492,32 @@ def run_collection(config, library, metadata, requested_collections, is_test, re
logger . info ( " " )
util . separator ( f " Finished { mapping_name } Collection \n Collection Run Time: { str ( datetime . now ( ) - collection_start ) . split ( ' . ' ) [ 0 ] } " )
logger . removeHandler ( collection_handler )
return resume_from
try :
if run or test or collections or libraries or resume :
start ( config_file , test, False , collections , libraries , resume )
start ( config_file , is_test= test , requested_collections = collections , requested_libraries = libraries , resume_from = resume )
else :
time_length = 0
schedule . every ( ) . day . at ( time_to_run ) . do ( start , config_file , False , True , None , None , None )
for time_to_run in times_to_run :
schedule . every ( ) . day . at ( time_to_run ) . do ( start , config_file , time_scheduled = time_to_run )
while True :
schedule . run_pending ( )
if not no_countdown :
current = datetime . now ( ) . strftime ( " % H: % M " )
seconds = ( datetime . strptime ( time_to_run , " % H: % M " ) - datetime . strptime ( current , " % H: % M " ) ) . total_seconds ( )
seconds = None
og_time_str = " "
for time_to_run in times_to_run :
new_seconds = ( datetime . strptime ( time_to_run , " % H: % M " ) - datetime . strptime ( current , " % H: % M " ) ) . total_seconds ( )
if new_seconds < 0 :
new_seconds + = 86400
if ( seconds is None or new_seconds < seconds ) and new_seconds > 0 :
seconds = new_seconds
og_time_str = time_to_run
hours = int ( seconds / / 3600 )
if hours < 0 :
hours + = 24
minutes = int ( ( seconds % 3600 ) / / 60 )
time_str = f " { hours } Hour { ' s ' if hours > 1 else ' ' } and " if hours > 0 else " "
time_str + = f " { minutes } Minute { ' s ' if minutes > 1 else ' ' } "
time_length = util . print_return ( time_length , f " Current Time: { current } | { time_str } until the daily run at { time_to_run } " )
time . sleep ( 1 )
time_length = util . print_return ( time_length , f " Current Time: { current } | { time_str } until the next run at { og_time_str } { times_to_run } " )
time . sleep ( 60 )
except KeyboardInterrupt :
util . separator ( " Exiting Plex Meta Manager " )