@ -26,6 +26,7 @@ parser.add_argument("-is", "--ignore-schedules", dest="ignore_schedules", help="
parser . add_argument ( " -ig " , " --ignore-ghost " , dest = " ignore_ghost " , help = " Run ignoring ghost logging " , action = " store_true " , default = False )
parser . add_argument ( " -ig " , " --ignore-ghost " , dest = " ignore_ghost " , help = " Run ignoring ghost logging " , 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 )
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 )
parser . add_argument ( " -co " , " --collection-only " , " --collections-only " , dest = " collection_only " , help = " Run only collection operations " , action = " store_true " , default = False )
parser . add_argument ( " -co " , " --collection-only " , " --collections-only " , dest = " collection_only " , help = " Run only collection operations " , action = " store_true " , default = False )
parser . add_argument ( " -po " , " --playlist-only " , " --playlists-only " , dest = " playlist_only " , help = " Run only playlist operations " , action = " store_true " , default = False )
parser . add_argument ( " -op " , " --operation " , " --operations " , " -lo " , " --library-only " , " --libraries-only " , " --operation-only " , " --operations-only " , dest = " operations " , help = " Run only operations " , action = " store_true " , default = False )
parser . add_argument ( " -op " , " --operation " , " --operations " , " -lo " , " --library-only " , " --libraries-only " , " --operation-only " , " --operations-only " , dest = " operations " , help = " Run only operations " , action = " store_true " , default = False )
parser . add_argument ( " -ov " , " --overlay " , " --overlays " , " --overlay-only " , " --overlays-only " , dest = " overlays " , help = " Run only overlays " , action = " store_true " , default = False )
parser . add_argument ( " -ov " , " --overlay " , " --overlays " , " --overlay-only " , " --overlays-only " , dest = " overlays " , help = " Run only overlays " , action = " store_true " , default = False )
parser . add_argument ( " -lf " , " --library-first " , " --libraries-first " , dest = " library_first " , help = " Run library operations before collections " , action = " store_true " , default = False )
parser . add_argument ( " -lf " , " --library-first " , " --libraries-first " , dest = " library_first " , help = " Run library operations before collections " , action = " store_true " , default = False )
@ -71,6 +72,7 @@ test = get_arg("PMM_TEST", args.test, arg_bool=True)
ignore_schedules = get_arg ( " PMM_IGNORE_SCHEDULES " , args . ignore_schedules , arg_bool = True )
ignore_schedules = get_arg ( " PMM_IGNORE_SCHEDULES " , args . ignore_schedules , arg_bool = True )
ignore_ghost = get_arg ( " PMM_IGNORE_GHOST " , args . ignore_ghost , arg_bool = True )
ignore_ghost = get_arg ( " PMM_IGNORE_GHOST " , args . ignore_ghost , arg_bool = True )
collection_only = get_arg ( " PMM_COLLECTIONS_ONLY " , args . collection_only , arg_bool = True )
collection_only = get_arg ( " PMM_COLLECTIONS_ONLY " , args . collection_only , arg_bool = True )
playlist_only = get_arg ( " PMM_PLAYLISTS_ONLY " , args . playlist_only , arg_bool = True )
operations_only = get_arg ( [ " PMM_OPERATIONS " , " PMM_LIBRARIES_ONLY " ] , args . operations , arg_bool = True )
operations_only = get_arg ( [ " PMM_OPERATIONS " , " PMM_LIBRARIES_ONLY " ] , args . operations , arg_bool = True )
overlays_only = get_arg ( [ " PMM_OVERLAYS " , " PMM_OVERLAYS_ONLY " ] , args . overlays , arg_bool = True )
overlays_only = get_arg ( [ " PMM_OVERLAYS " , " PMM_OVERLAYS_ONLY " ] , args . overlays , arg_bool = True )
library_first = get_arg ( " PMM_LIBRARIES_FIRST " , args . library_first , arg_bool = True )
library_first = get_arg ( " PMM_LIBRARIES_FIRST " , args . library_first , arg_bool = True )
@ -171,6 +173,7 @@ def start(attrs):
attrs [ " version " ] = version
attrs [ " version " ] = version
attrs [ " no_missing " ] = no_missing
attrs [ " no_missing " ] = no_missing
attrs [ " collection_only " ] = collection_only
attrs [ " collection_only " ] = collection_only
attrs [ " playlist_only " ] = playlist_only
attrs [ " operations_only " ] = operations_only
attrs [ " operations_only " ] = operations_only
attrs [ " overlays_only " ] = overlays_only
attrs [ " overlays_only " ] = overlays_only
logger . separator ( debug = True )
logger . separator ( debug = True )
@ -179,6 +182,7 @@ def start(attrs):
logger . debug ( f " --run (PMM_RUN): { run } " )
logger . debug ( f " --run (PMM_RUN): { run } " )
logger . debug ( f " --run-tests (PMM_TEST): { test } " )
logger . debug ( f " --run-tests (PMM_TEST): { test } " )
logger . debug ( f " --collections-only (PMM_COLLECTIONS_ONLY): { collection_only } " )
logger . debug ( f " --collections-only (PMM_COLLECTIONS_ONLY): { collection_only } " )
logger . debug ( f " --playlists-only (PMM_PLAYLISTS_ONLY): { playlist_only } " )
logger . debug ( f " --operations (PMM_OPERATIONS): { operations_only } " )
logger . debug ( f " --operations (PMM_OPERATIONS): { operations_only } " )
logger . debug ( f " --overlays (PMM_OVERLAYS): { overlays_only } " )
logger . debug ( f " --overlays (PMM_OVERLAYS): { overlays_only } " )
logger . debug ( f " --libraries-first (PMM_LIBRARIES_FIRST): { library_first } " )
logger . debug ( f " --libraries-first (PMM_LIBRARIES_FIRST): { library_first } " )
@ -208,7 +212,7 @@ def start(attrs):
logger . critical ( e )
logger . critical ( e )
else :
else :
try :
try :
stats = update_libraries ( config )
stats = run_config ( config )
except Exception as e :
except Exception as e :
config . notify ( e )
config . notify ( e )
logger . stacktrace ( )
logger . stacktrace ( )
@ -228,130 +232,12 @@ def start(attrs):
logger . separator ( f " Finished { start_type } Run \n { version_line } \n Finished: { end_time . strftime ( ' % H: % M: % S % Y- % m- %d ' ) } Run Time: { run_time } " )
logger . separator ( f " Finished { start_type } Run \n { version_line } \n Finished: { end_time . strftime ( ' % H: % M: % S % Y- % m- %d ' ) } Run Time: { run_time } " )
logger . remove_main_handler ( )
logger . remove_main_handler ( )
def update_libraries ( config ) :
def run_config ( config ) :
library_status = { }
library_status = run_libraries ( config ) if not playlist_only else { }
for library in config . libraries :
if library . skip_library :
logger . info ( " " )
logger . separator ( f " Skipping { library . name } Library " )
continue
library_status [ library . name ] = { }
try :
logger . add_library_handler ( library . mapping_name )
plexapi . server . TIMEOUT = library . timeout
logger . info ( " " )
logger . separator ( f " { library . name } Library " )
logger . debug ( " " )
logger . debug ( f " Mapping Name: { library . original_mapping_name } " )
logger . debug ( f " Folder Name: { library . mapping_name } " )
for ad in library . asset_directory :
logger . debug ( f " Asset Directory: { ad } " )
logger . debug ( f " Asset Folders: { library . asset_folders } " )
logger . debug ( f " Create Asset Folders: { library . create_asset_folders } " )
logger . debug ( f " Download URL Assets: { library . download_url_assets } " )
logger . debug ( f " Sync Mode: { library . sync_mode } " )
logger . debug ( f " Minimum Items: { library . minimum_items } " )
logger . debug ( f " Delete Below Minimum: { library . delete_below_minimum } " )
logger . debug ( f " Delete Not Scheduled: { library . delete_not_scheduled } " )
logger . debug ( f " Default Collection Order: { library . default_collection_order } " )
logger . debug ( f " Missing Only Released: { library . missing_only_released } " )
logger . debug ( f " Only Filter Missing: { library . only_filter_missing } " )
logger . debug ( f " Show Unmanaged: { library . show_unmanaged } " )
logger . debug ( f " Show Filtered: { library . show_filtered } " )
logger . debug ( f " Show Missing: { library . show_missing } " )
logger . debug ( f " Show Missing Assets: { library . show_missing_assets } " )
logger . debug ( f " Save Report: { library . save_report } " )
logger . debug ( f " Report Path: { library . report_path } " )
logger . debug ( f " Clean Bundles: { library . clean_bundles } " )
logger . debug ( f " Empty Trash: { library . empty_trash } " )
logger . debug ( f " Optimize: { library . optimize } " )
logger . debug ( f " Timeout: { library . timeout } " )
if config . delete_collections :
time_start = datetime . now ( )
logger . info ( " " )
logger . separator ( f " Deleting all Collections from the { library . name } Library " , space = False , border = False )
logger . info ( " " )
for collection in library . get_all_collections ( ) :
logger . info ( f " Collection { collection . title } Deleted " )
library . query ( collection . delete )
library_status [ library . name ] [ " All Collections Deleted " ] = str ( datetime . now ( ) - time_start ) . split ( ' . ' ) [ 0 ]
time_start = datetime . now ( )
temp_items = None
list_key = None
expired = None
if config . Cache and cache_libraries :
list_key , expired = config . Cache . query_list_cache ( " library " , library . mapping_name , 1 )
if list_key and expired is False :
logger . info ( f " Library: { library . mapping_name } loaded from Cache " )
temp_items = config . Cache . query_list_ids ( list_key )
if not temp_items :
temp_items = library . cache_items ( )
if config . Cache :
if list_key :
config . Cache . delete_list_ids ( list_key )
list_key = config . Cache . update_list_cache ( " library " , library . mapping_name , expired , 1 )
config . Cache . update_list_ids ( list_key , [ ( i . ratingKey , i . guid ) for i in temp_items ] )
if not library . is_other and not library . is_music :
logger . info ( " " )
logger . separator ( f " Mapping { library . name } Library " , space = False , border = False )
logger . info ( " " )
library . map_guids ( temp_items )
library_status [ library . name ] [ " Library Loading and Mapping " ] = str ( datetime . now ( ) - time_start ) . split ( ' . ' ) [ 0 ]
if config . library_first and not config . test_mode and not collection_only :
if not overlays_only and library . library_operation :
library_status [ library . name ] [ " Library Operations " ] = library . Operations . run_operations ( )
if not operations_only and ( library . overlay_files or library . remove_overlays ) :
library_status [ library . name ] [ " Library Overlays " ] = library . Overlays . run_overlays ( )
if not operations_only and not overlays_only :
time_start = datetime . now ( )
for metadata in library . metadata_files :
metadata_name = metadata . get_file_name ( )
if config . requested_metadata_files and metadata_name not in config . requested_metadata_files :
logger . info ( " " )
logger . separator ( f " Skipping { metadata_name } Metadata File " )
continue
logger . info ( " " )
logger . separator ( f " Running { metadata_name } Metadata File \n { metadata . path } " )
if not config . test_mode and not config . resume_from and not collection_only :
try :
metadata . update_metadata ( )
except Failed as e :
library . notify ( e )
logger . error ( e )
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: { config . resume_from } not in Metadata File: { metadata . path } " )
continue
if collections_to_run :
logger . info ( " " )
logger . separator ( f " { ' Test ' if config . test_mode else ' ' } Collections " )
logger . remove_library_handler ( library . mapping_name )
run_collection ( config , library , metadata , collections_to_run )
logger . re_add_library_handler ( library . mapping_name )
library_status [ library . name ] [ " Library Metadata Files " ] = str ( datetime . now ( ) - time_start ) . split ( ' . ' ) [ 0 ]
if not config . library_first and not config . test_mode and not collection_only :
if not overlays_only and library . library_operation :
library_status [ library . name ] [ " Library Operations " ] = library . Operations . run_operations ( )
if not operations_only and ( library . overlay_files or library . remove_overlays ) :
library_status [ library . name ] [ " Library Overlays " ] = library . Overlays . run_overlays ( )
logger . remove_library_handler ( library . mapping_name )
except Exception as e :
library . notify ( e )
logger . stacktrace ( )
logger . critical ( e )
playlist_status = { }
playlist_status = { }
playlist_stats = { }
playlist_stats = { }
if config . playlist_files or config . general [ " playlist_report " ] :
if ( config . playlist_files or config . general [ " playlist_report " ] ) and not overlays_only and not operations_only and not collection_only :
logger . add_playlists_handler ( )
logger . add_playlists_handler ( )
if config . playlist_files :
if config . playlist_files :
playlist_status , playlist_stats = run_playlists ( config )
playlist_status , playlist_stats = run_playlists ( config )
@ -375,14 +261,15 @@ def update_libraries(config):
logger . info ( f " { playlist_name : < { max_length } } | { ' all ' if len ( users ) == len ( library . users ) + 1 else ' , ' . join ( users ) } " )
logger . info ( f " { playlist_name : < { max_length } } | { ' all ' if len ( users ) == len ( library . users ) + 1 else ' , ' . join ( users ) } " )
logger . remove_playlists_handler ( )
logger . remove_playlists_handler ( )
amount_added = 0
if not operations_only and not overlays_only and not playlist_only :
has_run_again = False
has_run_again = False
for library in config . libraries :
for library in config . libraries :
if library . run_again :
if library . run_again :
has_run_again = True
has_run_again = True
break
break
amount_added = 0
if has_run_again :
if has_run_again and not operations_only and not overlays_only :
logger . info ( " " )
logger . info ( " " )
logger . separator ( " Run Again " )
logger . separator ( " Run Again " )
logger . info ( " " )
logger . info ( " " )
@ -416,6 +303,7 @@ def update_libraries(config):
logger . stacktrace ( )
logger . stacktrace ( )
logger . critical ( e )
logger . critical ( e )
if not collection_only and not overlays_only and not playlist_only :
used_url = [ ]
used_url = [ ]
for library in config . libraries :
for library in config . libraries :
if library . url not in used_url :
if library . url not in used_url :
@ -490,6 +378,128 @@ def update_libraries(config):
stats [ " names " ] . extend ( [ { " name " : n , " library " : " PLAYLIST " } for n in playlist_stats [ " names " ] ] )
stats [ " names " ] . extend ( [ { " name " : n , " library " : " PLAYLIST " } for n in playlist_stats [ " names " ] ] )
return stats
return stats
def run_libraries ( config ) :
library_status = { }
for library in config . libraries :
if library . skip_library :
logger . info ( " " )
logger . separator ( f " Skipping { library . name } Library " )
continue
library_status [ library . name ] = { }
try :
logger . add_library_handler ( library . mapping_name )
plexapi . server . TIMEOUT = library . timeout
logger . info ( " " )
logger . separator ( f " { library . name } Library " )
logger . debug ( " " )
logger . debug ( f " Mapping Name: { library . original_mapping_name } " )
logger . debug ( f " Folder Name: { library . mapping_name } " )
for ad in library . asset_directory :
logger . debug ( f " Asset Directory: { ad } " )
logger . debug ( f " Asset Folders: { library . asset_folders } " )
logger . debug ( f " Create Asset Folders: { library . create_asset_folders } " )
logger . debug ( f " Download URL Assets: { library . download_url_assets } " )
logger . debug ( f " Sync Mode: { library . sync_mode } " )
logger . debug ( f " Minimum Items: { library . minimum_items } " )
logger . debug ( f " Delete Below Minimum: { library . delete_below_minimum } " )
logger . debug ( f " Delete Not Scheduled: { library . delete_not_scheduled } " )
logger . debug ( f " Default Collection Order: { library . default_collection_order } " )
logger . debug ( f " Missing Only Released: { library . missing_only_released } " )
logger . debug ( f " Only Filter Missing: { library . only_filter_missing } " )
logger . debug ( f " Show Unmanaged: { library . show_unmanaged } " )
logger . debug ( f " Show Filtered: { library . show_filtered } " )
logger . debug ( f " Show Missing: { library . show_missing } " )
logger . debug ( f " Show Missing Assets: { library . show_missing_assets } " )
logger . debug ( f " Save Report: { library . save_report } " )
logger . debug ( f " Report Path: { library . report_path } " )
logger . debug ( f " Clean Bundles: { library . clean_bundles } " )
logger . debug ( f " Empty Trash: { library . empty_trash } " )
logger . debug ( f " Optimize: { library . optimize } " )
logger . debug ( f " Timeout: { library . timeout } " )
if config . delete_collections :
time_start = datetime . now ( )
logger . info ( " " )
logger . separator ( f " Deleting all Collections from the { library . name } Library " , space = False , border = False )
logger . info ( " " )
for collection in library . get_all_collections ( ) :
logger . info ( f " Collection { collection . title } Deleted " )
library . query ( collection . delete )
library_status [ library . name ] [ " All Collections Deleted " ] = str ( datetime . now ( ) - time_start ) . split ( ' . ' ) [ 0 ]
time_start = datetime . now ( )
temp_items = None
list_key = None
expired = None
if config . Cache and cache_libraries :
list_key , expired = config . Cache . query_list_cache ( " library " , library . mapping_name , 1 )
if list_key and expired is False :
logger . info ( f " Library: { library . mapping_name } loaded from Cache " )
temp_items = config . Cache . query_list_ids ( list_key )
if not temp_items :
temp_items = library . cache_items ( )
if config . Cache :
if list_key :
config . Cache . delete_list_ids ( list_key )
list_key = config . Cache . update_list_cache ( " library " , library . mapping_name , expired , 1 )
config . Cache . update_list_ids ( list_key , [ ( i . ratingKey , i . guid ) for i in temp_items ] )
if not library . is_other and not library . is_music :
logger . info ( " " )
logger . separator ( f " Mapping { library . name } Library " , space = False , border = False )
logger . info ( " " )
library . map_guids ( temp_items )
library_status [ library . name ] [ " Library Loading and Mapping " ] = str ( datetime . now ( ) - time_start ) . split ( ' . ' ) [ 0 ]
if config . library_first and not config . test_mode and not collection_only :
if not overlays_only and library . library_operation :
library_status [ library . name ] [ " Library Operations " ] = library . Operations . run_operations ( )
if not operations_only and ( library . overlay_files or library . remove_overlays ) :
library_status [ library . name ] [ " Library Overlays " ] = library . Overlays . run_overlays ( )
if not operations_only and not overlays_only :
time_start = datetime . now ( )
for metadata in library . metadata_files :
metadata_name = metadata . get_file_name ( )
if config . requested_metadata_files and metadata_name not in config . requested_metadata_files :
logger . info ( " " )
logger . separator ( f " Skipping { metadata_name } Metadata File " )
continue
logger . info ( " " )
logger . separator ( f " Running { metadata_name } Metadata File \n { metadata . path } " )
if not config . test_mode and not config . resume_from and not collection_only :
try :
metadata . update_metadata ( )
except Failed as e :
library . notify ( e )
logger . error ( e )
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: { config . resume_from } not in Metadata File: { metadata . path } " )
continue
if collections_to_run :
logger . info ( " " )
logger . separator ( f " { ' Test ' if config . test_mode else ' ' } Collections " )
logger . remove_library_handler ( library . mapping_name )
run_collection ( config , library , metadata , collections_to_run )
logger . re_add_library_handler ( library . mapping_name )
library_status [ library . name ] [ " Library Metadata Files " ] = str ( datetime . now ( ) - time_start ) . split ( ' . ' ) [ 0 ]
if not config . library_first and not config . test_mode and not collection_only :
if not overlays_only and library . library_operation :
library_status [ library . name ] [ " Library Operations " ] = library . Operations . run_operations ( )
if not operations_only and ( library . overlay_files or library . remove_overlays ) :
library_status [ library . name ] [ " Library Overlays " ] = library . Overlays . run_overlays ( )
logger . remove_library_handler ( library . mapping_name )
except Exception as e :
library . notify ( e )
logger . stacktrace ( )
logger . critical ( e )
return library_status
def run_collection ( config , library , metadata , requested_collections ) :
def run_collection ( config , library , metadata , requested_collections ) :
logger . info ( " " )
logger . info ( " " )
for mapping_name , collection_attrs in requested_collections . items ( ) :
for mapping_name , collection_attrs in requested_collections . items ( ) :