1
0
Fork 0

Added option -m/--mode to either cleanup all albums created by the script or delete all albums for that user

This commit is contained in:
Salvoxia 2024-07-31 20:59:59 +02:00
parent 870d34144c
commit 261f36065b

View file

@ -15,6 +15,15 @@ def is_integer(str):
except ValueError:
return False
# Constants holding script run modes
# Creat albums based on folder names and script arguments
SCRIPT_MODE_CREATE = "CREATE"
# Create album names based on folder names, but delete these albums
SCRIPT_MODE_CLEANUP = "CLEANUP"
# Delete ALL albums
SCRIPT_MODE_DELETE_ALL = "DELETE_ALL"
parser = argparse.ArgumentParser(description="Create Immich Albums from an external library path based on the top level folders", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("root_path", action='append', help="The external libarary's root path in Immich")
parser.add_argument("api_url", help="The root API URL of immich, e.g. https://immich.mydomain.com/api/")
@ -28,6 +37,7 @@ parser.add_argument("-C", "--fetch-chunk-size", default=5000, type=int, help="Ma
parser.add_argument("-l", "--log-level", default="INFO", choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'], help="Log level to use")
parser.add_argument("-k", "--insecure", action="store_true", help="Set to true to ignore SSL verification")
parser.add_argument("-i", "--ignore", default="", type=str, help="A string containing a list of folders, sub-folder sequences or file names separated by ':' that will be ignored.")
parser.add_argument("-m", "--mode", default=SCRIPT_MODE_CREATE, choices=[SCRIPT_MODE_CREATE, SCRIPT_MODE_CLEANUP, SCRIPT_MODE_DELETE_ALL], help="Mode for the script to run with. CREATE = Create albums based on folder names and provided arguments; CLEANUP = Create album nmaes based on current images and script arguments, but delete albums if they exist; DELETE_ALL = Delete all albums. If the mode is anything but CREATE, --unattended does not have any effect.")
args = vars(parser.parse_args())
# set up logger to log in logfmt format
logging.basicConfig(level=args["log_level"], stream=sys.stdout, format='time=%(asctime)s level=%(levelname)s msg=%(message)s')
@ -45,6 +55,12 @@ album_levels_range_arr = ()
album_level_separator = args["album_separator"]
insecure = args["insecure"]
ignore_albums = args["ignore"]
mode = args["mode"]
# Override unattended if we're running in destructive mode
if mode != SCRIPT_MODE_CREATE:
unattended = False
logging.debug("root_path = %s", root_paths)
logging.debug("root_url = %s", root_url)
logging.debug("api_key = %s", api_key)
@ -219,7 +235,11 @@ def fetchAssetsMinorV106():
assets = []
# prepare request body
body = {}
body['isNotInAlbum'] = 'true'
# only request images that are not in any album if we are running in CREATE mode,
# otherwise we need all images, even if they are part of an album
if mode == SCRIPT_MODE_CREATE:
body['isNotInAlbum'] = 'true'
# This API call allows a maximum page size of 1000
number_of_assets_to_fetch_per_request_search = min(1000, number_of_assets_to_fetch_per_request)
body['size'] = number_of_assets_to_fetch_per_request_search
@ -246,7 +266,7 @@ def fetchAssetsMinorV106():
return assets
# Fetches assets from the Immich API
# Fetches albums from the Immich API
# Takes different API versions into account for compatibility
def fetchAlbums():
apiEndpoint = 'albums'
@ -257,6 +277,20 @@ def fetchAlbums():
r.raise_for_status()
return r.json()
# Deletes an album identified by album['id']
# Takes different API versions into account for compatibility
# Returns False if the album could not be deleted, otherwise True
def deleteAlbum(album):
apiEndpoint = 'albums'
if version['major'] == 1 and version['minor'] <= 105:
apiEndpoint = 'album'
logging.debug("Album ID = %s, Album Name = %s", album['id'], album['albumName'])
r = requests.delete(root_url+apiEndpoint+'/'+album['id'], **requests_kwargs)
if r.status_code not in [200, 201]:
logging.error("Error deleting album %s: %s", album['albumName'], r.reason)
return False
return True
# Creates an album with the provided name and returns the ID of the
# created album
def createAlbum(albumName):
@ -309,6 +343,19 @@ if root_url[-1] != '/':
root_url = root_url + '/'
version = fetchServerVersion()
# Special case: Run Mode DELETE_ALL albums
if mode == SCRIPT_MODE_DELETE_ALL:
albums = fetchAlbums()
logging.info("%d existing albums identified", len(albums))
print("Going to delete ALL albums! Press enter to proceed, Ctrl+C to abort")
input()
cpt = 0
for album in albums:
if deleteAlbum(album):
logging.info("Deleted album %s", album['albumName'])
cpt += 1
logging.info("Deleted %d/%d albums", cpt, len(albums))
exit(0)
logging.info("Requesting all assets")
assets = fetchAssets()
@ -353,7 +400,10 @@ album_to_assets = {k:v for k, v in sorted(album_to_assets.items(), key=(lambda i
logging.info("%d albums identified", len(album_to_assets))
logging.info("Album list: %s", list(album_to_assets.keys()))
if not unattended:
print("Press Enter to continue, Ctrl+C to abort")
userHint = "Press enter to create these albums, Ctrl+C to abort"
if mode != SCRIPT_MODE_CREATE:
userHint = "Attention! Press enter to DELETE these albums (if they exist), Ctrl+C to abort"
print(userHint)
input()
@ -365,7 +415,22 @@ albums = fetchAlbums()
album_to_id = {album['albumName']:album['id'] for album in albums }
logging.info("%d existing albums identified", len(albums))
# mode CLEANUP
if mode == SCRIPT_MODE_CLEANUP:
cpt = 0
for album in album_to_assets:
if album in album_to_id:
album_to_delete = dict()
album_to_delete['id'] = album_to_id[album]
album_to_delete['albumName'] = album
if deleteAlbum(album_to_delete):
logging.info("Deleted album %s", album_to_delete['albumName'])
cpt += 1
logging.info("Deleted %d/%d albums", cpt, len(album_to_assets))
exit(0)
# mode CREATE
logging.info("Creating albums if needed")
cpt = 0
for album in album_to_assets: