Skip to content

Instantly share code, notes, and snippets.

@T-One
Created January 10, 2024 19:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save T-One/c857005e58286149914ad38f24a891e1 to your computer and use it in GitHub Desktop.
Save T-One/c857005e58286149914ad38f24a891e1 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import json
import requests
import argparse
from datetime import datetime
def prompt_for_credentials():
api_key = input('Enter API key: ')
immich_server = input('Enter full address including http and port (e.g. http://192.168.0.1:2283)').rstrip('/')
return api_key, immich_server
def parse_arguments():
parser = argparse.ArgumentParser(description='Fetch file report and delete orphaned assets.')
parser.add_argument('--api_key', help='API key for authentication')
parser.add_argument('--immich_server', help='Full address including port (http://192.168.0.1:2283)')
parser.add_argument('--no_prompt', action='store_true', help='Enable to delete orphaned assets without user confirmation')
parser.add_argument('--logfile', help='Filename to append deleted objects after successful deletion as json')
args = parser.parse_args()
# Manipulate the --immich_server argument to remove trailing slashes
if args.immich_server:
args.immich_server = args.immich_server.rstrip('/')
return args
def main():
args = parse_arguments()
if args.api_key and args.immich_server:
api_key, immich_server = args.api_key, args.immich_server
else:
api_key, immich_server = prompt_for_credentials()
base_url = f'{immich_server}/api'
file_report_url = base_url + '/audit/file-report'
headers = {'x-api-key': api_key}
response = requests.get(file_report_url, headers=headers)
response.raise_for_status()
# Check if there are orphaned person assets and print a message
person_assets = [
{'pathValue': person['pathValue'], 'entityId': person['entityId'], 'entityType': person['entityType']}
for person in response.json().get('orphans', []) if person.get('entityType') == 'person'
]
# Extracting "pathValue," "entityId," and "entityType" fields from the response for orphaned assets
orphans_data = [
{'pathValue': orphan['pathValue'], 'entityId': orphan['entityId'], 'entityType': orphan['entityType']}
for orphan in response.json().get('orphans', []) if orphan.get('entityType') == 'asset'
]
num_entries = len(orphans_data)
if num_entries == 0 and args.no_prompt:
return
if num_entries == 0 and not args.no_prompt:
print('Nothing to delete, stopping.')
return
if not args.no_prompt:
for data in orphans_data:
print('Path Value:', data['pathValue'])
print('Entity ID:', data['entityId'])
print('---')
if person_assets and not args.no_prompt:
print('Found Person assets, run job RECOGNIZE FACES All after this')
if not args.no_prompt and num_entries > 0:
summary = f'There are {num_entries} entries of orphaned data. Do you want to continue and delete the orphaned assets? (yes/no): '
user_input = input(summary).lower()
if user_input not in ('y', 'yes'):
print('Script execution aborted.')
return
# Deleting orphaned assets in a loop with a separate REST call for each entityId
for data in orphans_data:
entity_id = data['entityId']
asset_url = f'{base_url}/asset'
delete_payload = json.dumps({'force': True, 'ids': [entity_id]})
headers = {'Content-Type': 'application/json', 'x-api-key': api_key}
response = requests.delete(asset_url, headers=headers, data=delete_payload)
response.raise_for_status()
if not args.no_prompt:
print(f'Deleting asset with Entity ID {entity_id}')
# Append timestamp, entityId, and pathValue in JSON format to the logfile after successful deletion
if args.logfile:
deletiontime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_data = {'deletiontime': deletiontime, 'entityId': entity_id, 'pathValue': data['pathValue']}
with open(args.logfile, 'a') as log_file:
log_file.write(json.dumps(log_data, indent=2) + '\n')
if __name__ == '__main__':
main()
@mio-19
Copy link

mio-19 commented Jan 22, 2024

I am getting requests.exceptions.HTTPError: 400 Client Error: Bad Request for url:

@T-One
Copy link
Author

T-One commented Jan 22, 2024

I am getting requests.exceptions.HTTPError: 400 Client Error: Bad Request for url:

Are you sure the URL is correct in the format http://192.168.0.1:2283 ?

@mio-19
Copy link

mio-19 commented Jan 22, 2024

Yes, the script was able to list orphans

I am getting requests.exceptions.HTTPError: 400 Client Error: Bad Request for url:

Are you sure the URL is correct in the format http://192.168.0.1:2283 ?

@mergleh
Copy link

mergleh commented Jan 30, 2024

Running into the same issue here. In my scenario, I created a second user with their own library and I had to manually remove some files and now I'm trying to clean up those references in Immich. I can create an API key while logged in to that user's account, but I get a 401 when trying to run the script to remove the orphaned assets. When I create a key on my admin account, I can retrieve the orphaned asset list, but the removal fails. I expanded the logging a bit and I see this:

{"force": true, "ids": ["88d4c1a6-bb0f-4a7b-87c8-3f7b1a319c49"]}
<Response [400]>
b'{"message":"Not found or no asset.delete access","error":"Bad Request","statusCode":400}'

An error occurred: 400 Client Error: Bad Request for url: http://x.x.x.x:8082/api/asset
Deleting orphaned media assets:   0%|          | 0/3221 [00:00<?, ?asset/s]

I search for a few of the orphaned IDs on the /api/assets page and I see do not see any of the IDs (which explains the error). I logged in as the second user and checked the same endpoint and I get a drastically shorter list of assets (~250 total) but the user has ~35000 pictures in their library. For what it's worth, I successfully ran this script yesterday on my library but it is ~6500 items - not sure if that would make a difference.

This leads me to 2 questions that I can't answer...

  1. Is the /api/assets endpoint expected to return the list for all libraries when using the admin's API token, or just the admin user's library?
  2. Is there a point where the /api/assets endpoint can't return the full list of assets so the script will fail when it runs into an ID that "doesn't exist"?

@shamefulIguana8
Copy link

I was getting the same 400 Client Error: Bad Request for url: error.

The admin api key isn't able to delete other users' assets as per immich-app/immich#6788

My extremely hacky solution was to add another user_api_key parameter and use that in the request loop instead of api_key, deduplicating the entities and running that for each user. It works as awful as it is.

@T-One
Copy link
Author

T-One commented Jan 31, 2024

Hi,

thanks for the input, i will move this script into my git repo and add support for multiple API keys and users.
I keep you updated in here when done, i will probably can get my hands on it on the upcoming weekend.

@eded333
Copy link

eded333 commented Feb 5, 2024

I was getting the same 400 Client Error: Bad Request for url: error.

The admin api key isn't able to delete other users' assets as per immich-app/immich#6788

My extremely hacky solution was to add another user_api_key parameter and use that in the request loop instead of api_key, deduplicating the entities and running that for each user. It works as awful as it is.

Could you provide the modified code?

@Jinorex
Copy link

Jinorex commented Feb 27, 2024

@T-One
did you get the multi API-Key and user to work?
I have the same 400 Client Error: Bad Request for url: error.
I tryed adding a new parameter as @shamefulIguana8 mentioned. But I failed.

@eded333
Copy link

eded333 commented Mar 1, 2024

@T-One did you get the multi API-Key and user to work? I have the same 400 Client Error: Bad Request for url: error. I tryed adding a new parameter as @shamefulIguana8 mentioned. But I failed.

If you havent got many orphaned files, here I found a solution that works immich-app/immich#5040

curl -L -X DELETE 'https://yourdomain.com/api/asset' -H 'Content-Type: application/json' -H 'x-api-key: foobar' -d '{
"force": true,
"ids": [
"e21d7378-e16c-4e7b-89bb-32faeaca19a0"
]
}'

With the correct api key for each path or file id.

@Thoroslives
Copy link

Thoroslives commented Mar 29, 2024

Try this i made some changes and submitted a pull req to the devs to update the docs

https://gist.github.com/Thoroslives/ca5d8e1efd15111febc1e7b34ac72668

immich-app/immich#8350

@eded333
Copy link

eded333 commented Apr 4, 2024

Try this i made some changes and submitted a pull req to the devs to update the docs

https://gist.github.com/Thoroslives/ca5d8e1efd15111febc1e7b34ac72668

immich-app/immich#8350

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment