merge upstream
This commit is contained in:
commit
699cb8b030
2 changed files with 278 additions and 86 deletions
38
README.md
38
README.md
|
@ -1,10 +1,15 @@
|
|||
[![Docker][docker-image]][docker-url]
|
||||
|
||||
[docker-image]: https://img.shields.io/docker/pulls/salvoxia/immich-folder-album-creator.svg
|
||||
[docker-url]: https://hub.docker.com/r/salvoxia/immich-folder-album-creator/
|
||||
|
||||
# Immich Folder Album Creator
|
||||
|
||||
This is a python script designed to automatically create albums in [Immich](https://immich.app/) from a folder structure mounted into the Immich container.
|
||||
This is useful for automatically creating and populating albums for external libraries.
|
||||
Using the provided docker image, the script can simply be added to the Immich compose stack and run along the rest of Immich's containers.
|
||||
|
||||
__Current compatibility:__ Immich v1.103.x and below
|
||||
__Current compatibility:__ Immich v1.106.x and below
|
||||
|
||||
## Disclaimer
|
||||
This script is mostly based on the following original script: [REDVM/immich_auto_album.py](https://gist.github.com/REDVM/d8b3830b2802db881f5b59033cf35702)
|
||||
|
@ -39,7 +44,8 @@ options:
|
|||
Additional external libarary root path in Immich; May be specified multiple times for multiple import paths or external libraries. (default: None)
|
||||
-u, --unattended Do not ask for user confirmation after identifying albums. Set this flag to run script as a cronjob. (default: False)
|
||||
-a ALBUM_LEVELS, --album-levels ALBUM_LEVELS
|
||||
Number of sub-folders below the root path used for album name creation. Positive numbers start from top of the folder structure, negative numbers from the bottom. Cannot be 0. (default: 1)
|
||||
Number of sub-folders or range of sub-folder levels below the root path used for album name creation. Positive numbers start from top of the folder structure, negative numbers from the bottom. Cannot be 0. If a range should be set, the start level and end level must be separated by a comma
|
||||
like '<startLevel>,<endLevel>'. If negative levels are used in a range, <startLevel> must be less than or equal to <endLevel>. (default: 1)
|
||||
-s ALBUM_SEPARATOR, --album-separator ALBUM_SEPARATOR
|
||||
Separator string to use for compound album names created from nested folders. Only effective if -a is set to a value > 1 (default: )
|
||||
-c CHUNK_SIZE, --chunk-size CHUNK_SIZE
|
||||
|
@ -59,10 +65,11 @@ The environment variables are analoguous to the script's command line arguments.
|
|||
|
||||
| Environment varible | Mandatory? | Description |
|
||||
| :------------------- | :----------- | :------------ |
|
||||
| ROOT_PATH | yes | A single or a comma separated list of import paths for external libraries in Immich |
|
||||
| API_URL | yes | The root API URL of immich, e.g. https://immich.mydomain.com/api/ |
|
||||
| API_KEY | yes | The Immich API Key to use |
|
||||
| ALBUM_LEVELS | no | Number of sub-folders below the root path used for album name creation. Positive numbers start from top of the folder structure, negative numbers from the bottom. Cannot be 0. Refer to [How it works](#how-it-works) for a detailed explanation|
|
||||
| ROOT_PATH | yes | A single or a comma separated list of import paths for external libraries in Immich´|
|
||||
| API_URL | yes | The root API URL of immich, e.g. https://immich.mydomain.com/api/ |
|
||||
| API_KEY | yes | The Immich API Key to use
|
||||
| CRON_EXPRESSION | yes | A [crontab-style expression](https://crontab.guru/) (e.g. "0 * * * *") to perform album creation on a schedule (e.g. every hour). |
|
||||
| ALBUM_LEVELS | no | Number of sub-folders or range of sub-folder levels below the root path used for album name creation. Positive numbers start from top of the folder structure, negative numbers from the bottom. Cannot be 0. If a range should be set, the start level and end level must be separated by a comma. Refer to [How it works](#how-it-works) for a detailed explanation |
|
||||
| ALBUM_SEPARATOR | no | Separator string to use for compound album names created from nested folders. Only effective if -a is set to a value > 1 (default: " ") |
|
||||
| CHUNK_SIZE | no | Maximum number of assets to add to an album with a single API call (default: 2000) |
|
||||
| FETCH_CHUNK_SIZE | no | Maximum number of assets to fetch with a single API call (default: 5000) |
|
||||
|
@ -179,7 +186,24 @@ Albums created for `root_path = /external_libs/photos/Birthdays`:
|
|||
- `Jane` (containing all imags from `Birthdays/Jane`)
|
||||
- `Skiing 2023`
|
||||
|
||||
Note that with negative `album-levels` images from different parent folders will be mixed in the same album if they reside in folders with the same name (see `Vacation` in example above).
|
||||
### Album Level Ranges
|
||||
|
||||
It is possible to specify not just a nunmber for `album-levels`, but a range from level x to level y in the folder structure that should make up an album's name:
|
||||
`--album-levels="2,3"`
|
||||
The range is applied to the folder structure beneath `root_path` from the top for positive levels and from the bottom for negative levels.
|
||||
Suppose the following folder structure for an external library with the script's `root_path` set to `/external_libs/photos`:
|
||||
```
|
||||
/external_libs/photos/2020/2020 02 Feb/Vacation
|
||||
/external_libs/photos/2020/2020 08 Aug/Vacation
|
||||
```
|
||||
- `--album-levels="2,3"` will create albums (for this folder structure, this is equal to `--album-levels="-2"`)
|
||||
- `2020 02 Feb Facation`
|
||||
- `2020 08 Aug Vacation`
|
||||
- `--album-levels="2,2"` will create albums (for this folder structure, this is equal to `--album-levels="-2,-2"`)
|
||||
- `2020 02 Feb`
|
||||
- `2020 08 Aug`
|
||||
|
||||
⚠️ Note that with negative `album-levels` or album level ranges, images from different parent folders will be mixed in the same album if they reside in sub-folders with the same name (see `Vacation` in example above).
|
||||
|
||||
Since Immich does not support real nested albums ([yet?](https://github.com/immich-app/immich/discussions/2073)), neither does this script.
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import requests
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
|
@ -7,6 +6,14 @@ import datetime
|
|||
from collections import defaultdict
|
||||
import urllib3
|
||||
|
||||
# Trying to deal with python's isnumeric() function
|
||||
# not recognizing negative numbers
|
||||
def is_integer(str):
|
||||
try:
|
||||
int(str)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
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")
|
||||
|
@ -14,7 +21,7 @@ parser.add_argument("api_url", help="The root API URL of immich, e.g. https://im
|
|||
parser.add_argument("api_key", help="The Immich API Key to use")
|
||||
parser.add_argument("-r", "--root-path", action="append", help="Additional external libarary root path in Immich; May be specified multiple times for multiple import paths or external libraries.")
|
||||
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("-a", "--album-levels", default=1, type=int, help="Number of sub-folders below the root path used for album name creation. Positive numbers start from top of the folder structure, negative numbers from the bottom. Cannot be 0.")
|
||||
parser.add_argument("-a", "--album-levels", default="1", type=str, help="Number of sub-folders or range of sub-folder levels below the root path used for album name creation. Positive numbers start from top of the folder structure, negative numbers from the bottom. Cannot be 0. If a range should be set, the start level and end level must be separated by a comma like '<startLevel>,<endLevel>'. If negative levels are used in a range, <startLevel> must be less than or equal to <endLevel>.")
|
||||
parser.add_argument("-s", "--album-separator", default=" ", type=str, help="Separator string to use for compound album names created from nested folders. Only effective if -a is set to a value > 1")
|
||||
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")
|
||||
|
@ -32,6 +39,8 @@ number_of_images_per_request = args["chunk_size"]
|
|||
number_of_assets_to_fetch_per_request = args["fetch_chunk_size"]
|
||||
unattended = args["unattended"]
|
||||
album_levels = args["album_levels"]
|
||||
# Album Levels Range handling
|
||||
album_levels_range_arr = ()
|
||||
album_level_separator = args["album_separator"]
|
||||
ignore_ssl = args["ignore_ssl"]
|
||||
logging.debug("root_path = %s", root_paths)
|
||||
|
@ -40,27 +49,52 @@ logging.debug("api_key = %s", api_key)
|
|||
logging.debug("number_of_images_per_request = %d", number_of_images_per_request)
|
||||
logging.debug("number_of_assets_to_fetch_per_request = %d", number_of_assets_to_fetch_per_request)
|
||||
logging.debug("unattended = %s", unattended)
|
||||
logging.debug("album_levels = %d", album_levels)
|
||||
logging.debug("album_levels = %s", album_levels)
|
||||
#logging.debug("album_levels_range = %s", album_levels_range)
|
||||
logging.debug("album_level_separator = %s", album_level_separator)
|
||||
logging.debug("ignore_ssl = %s", ignore_ssl)
|
||||
|
||||
# Verify album levels
|
||||
if album_levels == 0:
|
||||
if is_integer(album_levels) and album_levels == 0:
|
||||
parser.print_help()
|
||||
exit(1)
|
||||
|
||||
if ignore_ssl:
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
# 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]
|
||||
|
||||
# Verify album levels range
|
||||
if not is_integer(album_levels):
|
||||
album_levels_range_split = album_levels.split(",")
|
||||
if (len(album_levels_range_split) != 2
|
||||
or not is_integer(album_levels_range_split[0])
|
||||
or not is_integer(album_levels_range_split[1])
|
||||
or int(album_levels_range_split[0]) == 0
|
||||
or int(album_levels_range_split[1]) == 0
|
||||
or (int(album_levels_range_split[0]) >= 0 and int(album_levels_range_split[1]) < 0)
|
||||
or (int(album_levels_range_split[0]) < 0 and int(album_levels_range_split[1]) >= 0)
|
||||
or (int(album_levels_range_split[0]) < 0 and int(album_levels_range_split[1]) < 0) and int(album_levels_range_split[0]) > int(album_levels_range_split[1])):
|
||||
logging.error("Invalid album_levels range format! If a range should be set, the start level and end level must be separated by a comma like '<startLevel>,<endLevel>'. If negative levels are used in a range, <startLevel> must be less than or equal to <endLevel>.")
|
||||
exit(1)
|
||||
album_levels_range_arr = album_levels_range_split
|
||||
# Convert to int
|
||||
album_levels_range_arr[0] = int(album_levels_range_split[0])
|
||||
album_levels_range_arr[1] = int(album_levels_range_split[1])
|
||||
# Special case: both levels are negative and end level is -1, which is equivalent to just negative album level of start level
|
||||
if(album_levels_range_arr[0] < 0 and album_levels_range_arr[1] == -1):
|
||||
album_levels = album_levels_range_arr[0]
|
||||
album_levels_range_arr = ()
|
||||
logging.debug("album_levels is a range with negative start level and end level of -1, converted to album_levels = %d", album_levels)
|
||||
else:
|
||||
logging.debug("valid album_levels range argument supplied")
|
||||
logging.debug("album_levels_start_level = %d", album_levels_range_arr[0])
|
||||
logging.debug("album_levels_end_level = %d", album_levels_range_arr[1])
|
||||
# Deduct 1 from album start levels, since album levels start at 1 for user convenience, but arrays start at index 0
|
||||
if album_levels_range_arr[0] > 0:
|
||||
album_levels_range_arr[0] -= 1
|
||||
album_levels_range_arr[1] -= 1
|
||||
|
||||
|
||||
# Request arguments for API calls
|
||||
requests_kwargs = {
|
||||
'headers' : {
|
||||
'x-api-key': api_key,
|
||||
|
@ -70,6 +104,195 @@ requests_kwargs = {
|
|||
'verify' : not ignore_ssl
|
||||
}
|
||||
|
||||
# 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]
|
||||
|
||||
# Create album names from provided path_chunks string array
|
||||
# based on supplied album_levels argument (either by level range or absolute album levels)
|
||||
def create_album_name(path_chunks):
|
||||
album_name_chunks = ()
|
||||
logging.debug("path chunks = %s", list(path_chunks))
|
||||
# Check which path to take: album_levels_range or album_levels
|
||||
if len(album_levels_range_arr) == 2:
|
||||
if album_levels_range_arr[0] < 0:
|
||||
album_levels_start_level_capped = min(len(path_chunks), abs(album_levels_range_arr[0]))
|
||||
album_levels_end_level_capped = album_levels_range_arr[1]+1
|
||||
album_levels_start_level_capped *= -1
|
||||
else:
|
||||
album_levels_start_level_capped = min(len(path_chunks)-1, album_levels_range_arr[0])
|
||||
# Add 1 to album_levels_end_level_capped to include the end index, which is what the user intended to. It's not a problem
|
||||
# if the end index is out of bounds.
|
||||
album_levels_end_level_capped = min(len(path_chunks)-1, album_levels_range_arr[1]) + 1
|
||||
logging.debug("album_levels_start_level_capped = %d", album_levels_start_level_capped)
|
||||
logging.debug("album_levels_end_level_capped = %d", album_levels_end_level_capped)
|
||||
# album start level is not equal to album end level, so we want a range of levels
|
||||
if album_levels_start_level_capped is not album_levels_end_level_capped:
|
||||
|
||||
# if the end index is out of bounds.
|
||||
if album_levels_end_level_capped < 0 and abs(album_levels_end_level_capped) >= len(path_chunks):
|
||||
album_name_chunks = path_chunks[album_levels_start_level_capped:]
|
||||
else:
|
||||
album_name_chunks = path_chunks[album_levels_start_level_capped:album_levels_end_level_capped]
|
||||
# album start and end levels are equal, we want exactly that level
|
||||
else:
|
||||
# create on-the-fly array with a single element taken from
|
||||
album_name_chunks = [path_chunks[album_levels_start_level_capped]]
|
||||
else:
|
||||
album_levels_int = int(album_levels)
|
||||
# either use as many path chunks as we have,
|
||||
# or the specified album levels
|
||||
album_name_chunk_size = min(len(path_chunks), abs(album_levels_int))
|
||||
if album_levels_int < 0:
|
||||
album_name_chunk_size *= -1
|
||||
|
||||
# Copy album name chunks from the path to use as album name
|
||||
album_name_chunks = path_chunks[:album_name_chunk_size]
|
||||
if album_name_chunk_size < 0:
|
||||
album_name_chunks = path_chunks[album_name_chunk_size:]
|
||||
logging.debug("album_name_chunks = %s", album_name_chunks)
|
||||
return album_level_separator.join(album_name_chunks)
|
||||
|
||||
# Fetches assets from the Immich API
|
||||
# Takes different API versions into account for compatibility
|
||||
def fetchServerVersion():
|
||||
# This API call was only introduced with version 1.106.1, so it will fail
|
||||
# for older versions.
|
||||
# Initialize the version with the latest version without this API call
|
||||
version = {'major': 1, 'minor': 105, "patch": 1}
|
||||
r = requests.get(root_url+'server-info/version', **requests_kwargs)
|
||||
assert r.status_code == 200 or r.status_code == 404
|
||||
if r.status_code == 200:
|
||||
version = r.json()
|
||||
logging.info("Detected Immich server version %s.%s.%s", version['major'], version['minor'], version['patch'])
|
||||
else:
|
||||
logging.info("Detected Immich server version %s.%s.%s or older", version['major'], version['minor'], version['patch'])
|
||||
return version
|
||||
|
||||
# Fetches assets from the Immich API
|
||||
# Takes different API versions into account for compatibility
|
||||
def fetchAssets():
|
||||
if version['major'] == 1 and version['minor'] <= 105:
|
||||
return fetchAssetsLegacy()
|
||||
else:
|
||||
return fetchAssetsMinorV106()
|
||||
|
||||
|
||||
# Fetches assets from the Immich API
|
||||
# Uses the legacy GET /asset call which only exists up to v1.105.x
|
||||
def fetchAssetsLegacy():
|
||||
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
|
||||
logging.debug("Received %s assets with chunk 1", len(r.json()))
|
||||
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():
|
||||
logging.info("Non-chunked Immich API detected, stopping fetching assets since we already got all in our first call")
|
||||
break
|
||||
assert r.status_code == 200
|
||||
logging.debug("Received %s assets with chunk", len(r.json()))
|
||||
assets = assets + r.json()
|
||||
return assets
|
||||
|
||||
# Fetches assets from the Immich API
|
||||
# Uses the /search/meta-data call. Much more efficient than the legacy method
|
||||
# since this call allows to filter for assets that are not in an album only.
|
||||
def fetchAssetsMinorV106():
|
||||
assets = []
|
||||
# prepare request body
|
||||
body = {}
|
||||
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
|
||||
# Initial API call, let's fetch our first chunk
|
||||
page = 1
|
||||
body['page'] = str(page)
|
||||
r = requests.post(root_url+'search/metadata', json=body, **requests_kwargs)
|
||||
r.raise_for_status()
|
||||
responseJson = r.json()
|
||||
assetsReceived = responseJson['assets']['items']
|
||||
logging.debug("Received %s assets with chunk %s", len(assetsReceived), page)
|
||||
|
||||
assets = assets + assetsReceived
|
||||
# If we got a full chunk size back, let's perfrom subsequent calls until we get less than a full chunk size
|
||||
while len(assetsReceived) == number_of_assets_to_fetch_per_request_search:
|
||||
page += 1
|
||||
body['page'] = page
|
||||
r = requests.post(root_url+'search/metadata', json=body, **requests_kwargs)
|
||||
assert r.status_code == 200
|
||||
responseJson = r.json()
|
||||
assetsReceived = responseJson['assets']['items']
|
||||
logging.debug("Received %s assets with chunk %s", len(assetsReceived), page)
|
||||
assets = assets + assetsReceived
|
||||
return assets
|
||||
|
||||
|
||||
# Fetches assets from the Immich API
|
||||
# Takes different API versions into account for compatibility
|
||||
def fetchAlbums():
|
||||
apiEndpoint = 'albums'
|
||||
if version['major'] == 1 and version['minor'] <= 105:
|
||||
apiEndpoint = 'album'
|
||||
|
||||
r = requests.get(root_url+apiEndpoint, **requests_kwargs)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
# Creates an album with the provided name and returns the ID of the
|
||||
# created album
|
||||
def createAlbum(albumName):
|
||||
apiEndpoint = 'albums'
|
||||
if version['major'] == 1 and version['minor'] <= 105:
|
||||
apiEndpoint = 'album'
|
||||
data = {
|
||||
'albumName': albumName,
|
||||
'description': albumName
|
||||
}
|
||||
r = requests.post(root_url+apiEndpoint, json=data, **requests_kwargs)
|
||||
assert r.status_code in [200, 201]
|
||||
return r.json()['id']
|
||||
|
||||
# Adds the provided assetIds to the provided albumId
|
||||
def addAssetsToAlbum(albumId, assets):
|
||||
apiEndpoint = 'albums'
|
||||
if version['major'] == 1 and version['minor'] <= 105:
|
||||
apiEndpoint = 'album'
|
||||
# Divide our assets into chunks of number_of_images_per_request,
|
||||
# So the API can cope
|
||||
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+apiEndpoint+f'/{albumId}/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':
|
||||
logging.warning("Error adding an asset to an album: %s", res['error'])
|
||||
else:
|
||||
cpt += 1
|
||||
if cpt > 0:
|
||||
logging.info("%d new assets added to %s", cpt, album)
|
||||
|
||||
# append trailing slash to all root paths
|
||||
for i in range(len(root_paths)):
|
||||
if root_paths[i][-1] != '/':
|
||||
|
@ -78,25 +301,10 @@ for i in range(len(root_paths)):
|
|||
if root_url[-1] != '/':
|
||||
root_url = root_url + '/'
|
||||
|
||||
logging.info("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
|
||||
logging.debug("Received %s assets with chunk 1", len(r.json()))
|
||||
assets = assets + r.json()
|
||||
version = fetchServerVersion()
|
||||
|
||||
# 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():
|
||||
logging.info("Non-chunked Immich API detected, stopping fetching assets since we already got all in our first call")
|
||||
break
|
||||
assert r.status_code == 200
|
||||
logging.debug("Received %s assets with chunk", len(r.json()))
|
||||
assets = assets + r.json()
|
||||
logging.info("Requesting all assets")
|
||||
assets = fetchAssets()
|
||||
logging.info("%d photos found", len(assets))
|
||||
|
||||
|
||||
|
@ -116,21 +324,11 @@ for asset in assets:
|
|||
|
||||
# remove last item from path chunks, which is the file name
|
||||
del path_chunks[-1]
|
||||
album_name_chunks = ()
|
||||
# either use as many path chunks as we have,
|
||||
# or the specified album levels
|
||||
album_name_chunk_size = min(len(path_chunks), album_levels)
|
||||
if album_levels < 0:
|
||||
album_name_chunk_size = min(len(path_chunks), abs(album_levels))*-1
|
||||
|
||||
# Copy album name chunks from the path to use as album name
|
||||
album_name_chunks = path_chunks[:album_name_chunk_size]
|
||||
if album_name_chunk_size < 0:
|
||||
album_name_chunks = path_chunks[album_name_chunk_size:]
|
||||
|
||||
album_name = album_level_separator.join(album_name_chunks)
|
||||
# Check that the extracted album name is not actually a file name in root_path
|
||||
album_to_assets[album_name].append(asset['id'])
|
||||
album_name = create_album_name(path_chunks)
|
||||
if len(album_name) > 0:
|
||||
album_to_assets[album_name].append(asset['id'])
|
||||
else:
|
||||
logging.warning("Got empty album name for asset path %s, check your album_level settings!", asset_path)
|
||||
|
||||
album_to_assets = {k:v for k, v in sorted(album_to_assets.items(), key=(lambda item: item[0]))}
|
||||
|
||||
|
@ -144,9 +342,8 @@ if not unattended:
|
|||
album_to_id = {}
|
||||
|
||||
logging.info("Listing existing albums on immich")
|
||||
r = requests.get(root_url+'album', **requests_kwargs)
|
||||
assert r.status_code == 200
|
||||
albums = r.json()
|
||||
|
||||
albums = fetchAlbums()
|
||||
album_to_id = {album['albumName']:album['id'] for album in albums }
|
||||
logging.info("%d existing albums identified", len(albums))
|
||||
|
||||
|
@ -156,13 +353,7 @@ 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']
|
||||
album_to_id[album] = createAlbum(album)
|
||||
logging.info('Album %s added!', album)
|
||||
cpt += 1
|
||||
logging.info("%d albums created", cpt)
|
||||
|
@ -173,29 +364,6 @@ logging.info("Adding assets to albums")
|
|||
# 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]
|
||||
|
||||
# Divide our assets into chunks of number_of_images_per_request,
|
||||
# So the API can cope
|
||||
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':
|
||||
logging.warning("Error adding an asset to an album: %s", res['error'])
|
||||
else:
|
||||
cpt += 1
|
||||
if cpt > 0:
|
||||
logging.info("%d new assets added to %s", cpt, album)
|
||||
addAssetsToAlbum(id, assets)
|
||||
|
||||
logging.info("Done!")
|
||||
|
|
Loading…
Reference in a new issue