#!/usr/bin/env python3 """Delete corrupted versions of files from Immich after good versions were uploaded.""" import requests import sys API_KEY = "GsWQUTR6EXlkKp1M82jDJ3KmzhM0fMAbbIbfHDyI" BASE_URL = "http://localhost:2283/api" # Map of filename -> corrupted path from corrupted_files.txt CORRUPTED_PATHS = { "2010-04-03_14-07-26_406.mp4": "/data/library/admin/2010/04/2010-04-03_14-07-26_406+1.mp4", "2010-04-03_14-07-52_756_Utrecht.mp4": "/data/library/admin/2010/04/2010-04-03_14-07-52_756_Utrecht+1.mp4", "2010-04-04_16-02-21_184_Noordoostpolder.mp4": "/data/library/admin/2010/04/2010-04-04_16-02-21_184_Noordoostpolder+1.mp4", "2010-04-04_16-02-44_615_Noordoostpolder.mp4": "/data/library/admin/2010/04/2010-04-04_16-02-44_615_Noordoostpolder+1.mp4", "2010-04-16_17-22-35_167_Noordoostpolder.mp4": "/data/library/admin/2010/04/2010-04-16_17-22-35_167_Noordoostpolder+1.mp4", "des (1).AVI": "/data/library/admin/2012/09/des (1).avi", "des (11).AVI": "/data/library/admin/2012/09/des (11).avi", "des (12).AVI": "/data/library/admin/2012/09/des (12).avi", "des (13).AVI": "/data/library/admin/2012/09/des (13).avi", "des (14).AVI": "/data/library/admin/2012/09/des (14).avi", "des (15).AVI": "/data/library/admin/2012/09/des (15).avi", "des (16).AVI": "/data/library/admin/2012/09/des (16).avi", "des (2).AVI": "/data/library/admin/2012/09/des (2).avi", "des (6).AVI": "/data/library/admin/2012/09/des (6).avi", "des (7).AVI": "/data/library/admin/2012/09/des (7).avi", "IMG_0024.MOV": "/data/library/admin/2010/11/IMG_0024+1.mov", "IMG_0067.MOV": "/data/library/admin/2010/11/IMG_0067+1.mov", "IMG_0146.MOV": "/data/library/admin/2011/06/IMG_0146+1.mov", "IMG_0148.MOV": "/data/library/admin/2011/06/IMG_0148+1.mov", "IMG_0149.MOV": "/data/library/admin/2011/06/IMG_0149+1.mov", "IMG_0156.MOV": "/data/library/admin/2011/06/IMG_0156+1.mov", "IMG_0157.MOV": "/data/library/admin/2011/06/IMG_0157+1.mov", "IMG_0164.MOV": "/data/library/admin/2011/06/IMG_0164+1.mov", "IMG_0165.MOV": "/data/library/admin/2011/06/IMG_0165+1.mov", "IMG_0172.MOV": "/data/library/admin/2010/08/IMG_0172+1.mov", "IMG_0178.MOV": "/data/library/admin/2010/08/IMG_0178+1.mov", "IMG_0179.MOV": "/data/library/admin/2011/07/IMG_0179+1.mov", "IMG_0182.MOV": "/data/library/admin/2010/08/IMG_0182+1.mov", "IMG_0183.MOV": "/data/library/admin/2010/08/IMG_0183+1.mov", "IMG_0184.MOV": "/data/library/admin/2010/08/IMG_0184+1.mov", "IMG_0185.MOV": "/data/library/admin/2010/08/IMG_0185+1.mov", "IMG_0187.MOV": "/data/library/admin/2010/08/IMG_0187+1.mov", "IMG_0554.MOV": "/data/library/admin/2012/09/IMG_0554+1.mov", "IMG_0555.MOV": "/data/library/admin/2012/09/IMG_0555+1.mov", "IMG_0558.MOV": "/data/library/admin/2012/09/IMG_0558+1.mov", "IMG_0581.MOV": "/data/library/admin/2012/11/IMG_0581+1.mov", "IMG_0584.MOV": "/data/library/admin/2012/11/IMG_0584+1.mov", "IMG_0586.MOV": "/data/library/admin/2012/11/IMG_0586+1.mov", "IMG_0591.MOV": "/data/library/admin/2012/11/IMG_0591+1.mov", "MVI_1077.AVI": "/data/library/admin/2012/09/MVI_1077.avi", "MVI_1079.AVI": "/data/library/admin/2012/09/MVI_1079.avi", "MVI_1080.AVI": "/data/library/admin/2012/09/MVI_1080.avi", "MVI_1085.AVI": "/data/library/admin/2012/09/MVI_1085.avi", } def search_file(filename): """Search for a file by name.""" resp = requests.post( f"{BASE_URL}/search/metadata", headers={"x-api-key": API_KEY, "Content-Type": "application/json"}, json={"originalFileName": filename} ) if resp.status_code == 200: data = resp.json() return data.get("assets", {}).get("items", []) return [] def delete_asset(asset_id, filename): """Delete an asset by ID.""" resp = requests.delete( f"{BASE_URL}/assets", headers={"x-api-key": API_KEY, "Content-Type": "application/json"}, json={"ids": [asset_id], "force": True} ) if resp.status_code == 204: print(f" DELETED: {filename}") return True else: print(f" FAILED to delete {filename}: {resp.status_code} {resp.text}") return False def main(): deleted = 0 not_found = 0 errors = 0 for filename, corrupted_path in CORRUPTED_PATHS.items(): print(f"\nProcessing: {filename}") # Search for all versions of this file assets = search_file(filename) if not assets: print(f" No assets found for {filename}") not_found += 1 continue print(f" Found {len(assets)} matches") # Find the one with the corrupted path (case-insensitive compare) corrupted_path_lower = corrupted_path.lower() found_corrupted = False for asset in assets: asset_path = asset.get("originalPath", "") print(f" - {asset['id'][:8]}... path={asset_path}") if asset_path.lower() == corrupted_path_lower: print(f" Found corrupted version at: {asset_path}") if delete_asset(asset["id"], filename): deleted += 1 found_corrupted = True else: errors += 1 break if not found_corrupted and len(assets) > 0: # Maybe already deleted or path doesn't match exactly print(f" WARNING: Could not find corrupted path {corrupted_path}") # Check if any has +1 in path (corrupted marker) for asset in assets: asset_path = asset.get("originalPath", "") if "+1" in asset_path or "/2012/09/" in asset_path: print(f" Found likely corrupted version at: {asset_path}") if delete_asset(asset["id"], filename): deleted += 1 else: errors += 1 break print(f"\n\nSummary:") print(f" Deleted: {deleted}") print(f" Not found: {not_found}") print(f" Errors: {errors}") if __name__ == "__main__": main()