Skip to content

Instantly share code, notes, and snippets.

@Salvoxia
Forked from REDVM/immich_auto_album.py
Last active May 1, 2024 16:40
Show Gist options
  • Save Salvoxia/1a0074a7c7e8817e1e2ac5a4bf8af66c to your computer and use it in GitHub Desktop.
Save Salvoxia/1a0074a7c7e8817e1e2ac5a4bf8af66c to your computer and use it in GitHub Desktop.
Create and populate albums on Immich based on folder name (comaptible with Immich v1.95.x), moved to here: https://github.com/Salvoxia/immich-folder-album-creator
import requests
import os
import argparse
from collections import defaultdict
# I have photos in subfolders like :
# /mnt/media/Photos/2023-08 Holidays
# /mnt/media/Photos/2023-06 Birthday
# /mnt/media/Photos/2022-12 Christmas
# This script will create 3 albums
# 2023-08 Holidays, 2023-06 Birthday, 2022-12 Christmas
# And populate them with the photos inside
# The script can be run multiple times to update, new albums will be created,
# or new photos added in existing subfolder will be added to corresponding album
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", 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/")
parser.add_argument("api_key", help="The Immich API Key to use")
parser.add_argument("-u", "--unattended", action="store_true", help="Do not ask for user confirmation after identifying albums. Set this flag to run script as a cronjob.")
parser.add_argument("-c", "--chunk-size", default=2000, type=int, help="Maximum number of assets to add to an album with a single API call")
parser.add_argument("-C", "--fetch-chunk-size", default=5000, type=int, help="Maximum number of assets to fetch with a single API call")
args = vars(parser.parse_args())
root_path = args["root_path"]
root_url = args["api_url"]
api_key = args["api_key"]
number_of_images_per_request = args["chunk_size"]
number_of_assets_to_fetch_per_request = args["fetch_chunk_size"]
unattended = args["unattended"]
# Yield successive n-sized
# chunks from l.
def divide_chunks(l, n):
# looping till length l
for i in range(0, len(l), n):
yield l[i:i + n]
requests_kwargs = {
'headers' : {
'x-api-key': api_key,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
if root_path[-1] != '/':
root_path = root_path + '/'
if root_url[-1] != '/':
root_url = root_url + '/'
print(" 1. Requesting all assets")
assets = []
# Initial API call, let's fetch our first chunk
r = requests.get(root_url+'asset?take='+str(number_of_assets_to_fetch_per_request), **requests_kwargs)
assert r.status_code == 200
assets = assets + r.json()
# If we got a full chunk size back, let's perfrom subsequent calls until we get less than a full chunk size
skip = 0
while len(r.json()) == number_of_assets_to_fetch_per_request:
skip += number_of_assets_to_fetch_per_request
r = requests.get(root_url+'asset?take='+str(number_of_assets_to_fetch_per_request)+'&skip='+str(skip), **requests_kwargs)
if skip == number_of_assets_to_fetch_per_request and assets == r.json():
print("Non-chunked Immich API detected, stopping fetching assets since we already got all in our first call")
break
assert r.status_code == 200
assets = assets + r.json()
print(len(assets), "photos found")
print(" 2. Sorting assets to corresponding albums using folder name")
album_to_assets = defaultdict(list)
for asset in assets:
asset_path = asset['originalPath']
if root_path not in asset_path:
continue
album_name = asset_path.replace(root_path, '').split('/')[0]
# Check that the extracted album name is not actually a file name in root_path
if not asset_path.endswith(album_name):
album_to_assets[album_name].append(asset['id'])
album_to_assets = {k:v for k, v in sorted(album_to_assets.items(), key=(lambda item: item[0]))}
print(len(album_to_assets), "albums identified")
print(list(album_to_assets.keys()))
if not unattended:
print("Press Enter to continue, Ctrl+C to abort")
input()
album_to_id = {}
print(" 3. Listing existing albums on immich")
r = requests.get(root_url+'album', **requests_kwargs)
assert r.status_code == 200
albums = r.json()
album_to_id = {album['albumName']:album['id'] for album in albums }
print(len(albums), "existing albums identified")
print(" 4. Creating albums if needed")
cpt = 0
for album in album_to_assets:
if album in album_to_id:
continue
data = {
'albumName': album,
'description': album
}
r = requests.post(root_url+'album', json=data, **requests_kwargs)
assert r.status_code in [200, 201]
album_to_id[album] = r.json()['id']
print(album, 'album added!')
cpt += 1
print(cpt, "albums created")
print(" 5. Adding assets to albums")
# Note: immich manage duplicates without problem,
# so we can each time ad all assets to same album, no photo will be duplicated
for album, assets in album_to_assets.items():
id = album_to_id[album]
assets_chunked = list(divide_chunks(assets, number_of_images_per_request))
for assets_chunk in assets_chunked:
data = {'ids':assets_chunk}
r = requests.put(root_url+f'album/{id}/assets', json=data, **requests_kwargs)
if r.status_code not in [200, 201]:
print(album)
print(r.json())
print(data)
continue
assert r.status_code in [200, 201]
response = r.json()
cpt = 0
for res in response:
if not res['success']:
if res['error'] != 'duplicate':
print("Warning, error in adding an asset to an album:", res['error'])
else:
cpt += 1
if cpt > 0:
print(f"{str(cpt).zfill(3)} new assets added to {album}")
print("Done!")
@Salvoxia
Copy link
Author

usage: immich_auto_album.py [-h] [-u] [-c CHUNK_SIZE] [-C FETCH_CHUNK_SIZE] root_path api_url api_key

Create Immich Albums from an external library path based on the top level folders

positional arguments:
  root_path             The external libarary's root path in Immich
  api_url               The root API URL of immich, e.g. https://immich.mydomain.com/api/
  api_key               The Immich API Key to use

options:
  -h, --help            show this help message and exit
  -u, --unattended      Do not ask for user confirmation after identifying albums. Set this flag to run script as a cronjob. (default: False)
  -c CHUNK_SIZE, --chunk-size CHUNK_SIZE
                        Maximum number of assets to add to an album with a single API call (default: 2000)
  -C FETCH_CHUNK_SIZE, --fetch-chunk-size FETCH_CHUNK_SIZE
                        Maximum number of assets to fetch with a single API call (default: 5000)

@haldi4803
Copy link

haldi4803 commented Feb 21, 2024

i think there is another Limit at work...

It does detect all 17297 Fotos in the Library, but only 12 Albums of the 63 Folders there should be.
1566 Photos to be specific.

  1. Requesting all assets
17297 photos found
  2. Sorting assets to corresponding albums using folder name
12 albums identified
['Censored 2015', 'Bardonecchia 09-2008', 'Basler Fasnacht 08', 'Crans-Montana Dezember 2008', 'Emmental', 'Ferien Griechenland 2005', 'Kroatien 07-2010', 'Kroatien 2007', 'Marokko Album 2007-12', 'Montenegro+Albanien 07-2008', 'Samnaun Februar 2009', 'Censored2 2015']
Press Enter to continue, Ctrl+C to abort

  3. Listing existing albums on immich
2 existing albums identified
  4. Creating albums if needed
Censored 2015 album added!
Bardonecchia 09-2008 album added!
Basler Fasnacht 08 album added!
Crans-Montana Dezember 2008 album added!
Emmental album added!
Ferien Griechenland 2005 album added!
Kroatien 07-2010 album added!
Kroatien 2007 album added!
Marokko Album 2007-12 album added!
Montenegro+Albanien 07-2008 album added!
Samnaun Februar 2009 album added!
Censored2 2015 album added!
12 albums created
  5. Adding assets to albums
014 new assets added to Censored 2015
134 new assets added to Bardonecchia 09-2008
022 new assets added to Basler Fasnacht 08
065 new assets added to Crans-Montana Dezember 2008
027 new assets added to Emmental
119 new assets added to Ferien Griechenland 2005
097 new assets added to Kroatien 07-2010
078 new assets added to Kroatien 2007
538 new assets added to Marokko Album 2007-12
441 new assets added to Montenegro+Albanien 07-2008
022 new assets added to Samnaun Februar 2009
009 new assets added to Censored2 2015
Done!

EDIT: Oh... big Question!
Do Foldernames with Points in it work?
FolderA 2022.10 ?
If i look at the albums that worked and those that don't that might be the issue?

Edit2: Jep... That seems to be the issue! The old Albums the Old script created are still there with a point in it!

@Salvoxia
Copy link
Author

Salvoxia commented Feb 21, 2024

Hm, for me it detected all of my 229 albums.
Has the script detected more albums than that with a previous version of Immich?
Are all of your folders on the same level as the 12 ones it detected, i.e. in root_path? The script does not create albums for nested folders.

@haldi4803
Copy link

Yes. All 63 Folders in the same root.
The old script did find the 2022.10 but the new one now only detects 2022-10

@Salvoxia
Copy link
Author

Ah, that's a good hint! If all of your missing folders contain a ., that should now be fixed in the updated revision of the script.
I added a check to prevent albums being created from files lying around in root_path, and that check accidentally also filtered out folders containing a ..

@Salvoxia
Copy link
Author

Maintenance and development of this script has been moved to this repository: Salvoxia/immich-folder-album-creator

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