139 lines
6.1 KiB
Python
139 lines
6.1 KiB
Python
#!/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()
|