Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save davidacampos/47138edcdc3ac040c05e8f3d6f6a9224 to your computer and use it in GitHub Desktop.
Save davidacampos/47138edcdc3ac040c05e8f3d6f6a9224 to your computer and use it in GitHub Desktop.
/**
DISCLAIMER: DO NOT COPY & PASTE RANDOM JAVASCRIPT SCRIPTS INTO YOUR BROWSER'S CONSOLE WITHOUT UNDERSTANDING WHAT THEY ARE DOING. THEY CAN STEAL YOUR PERSONAL INFORMATION!!!
What this script does?
Based on an external library in immich, it goes through all the folder of it, and creates albums for each one
How it works?
1- Gets and deletes any existing albums, whose description match the one used when generating them (if any... this is for the only purpose of "regenerating" the folders if the script is run more than once)
2- Gets all existing assets (i.e. photos and videos). This might take a while, in my case the generated JSON was over 100MB.
3- Goes through each one of them, and if they have the originalPath value (I assume that's only for external? I don't have non-external assets) extract the top folder from it and add it to our list of albums to create.
4- With the list of top folders, create albums and include the assets as needed.
How to run?
1- Configure the PARAMETERS section as needed (the critical options are rootPath and albumGenerationLimit).
2- In a web browser, open (and log in) your Immich instance.
3- Open the browser's console (pressing F12 in Chrome in Windows) and copy & paste, then press enter.
4- Progress will be shown in the console's output
*/
(async function () {
//** PARAMETERS */
/***********************************************************/
let immichHost = ""; // This could be left blank if running the script within the same immich instance or something like https://IMMICH_HOST
let rootPath = "/mnt/pictures/"; // This will be used as the "root" folder... meaning albums will be created based on folders directly under "root" (e.g. /mnt/pictures/ChristmasHoliday will end up as album "ChristmasHoliday")
let albumDescription = "Auto-generated from external library"; // This is needed to use as a key when/if running the script multiple times
let foldersToExclude = []; // Optional, this is to exclude folders from been created as albums
let albumGenerationLimit = 5; // Max number of albums auto-generated. Need to change to something like 9999 to create all albums
/***********************************************************/
// Get all albums
console.log("Fetching all albums...");
let albums = await (await fetch(immichHost + "/api/album", {
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
})).json();
// Go through all albums, and delete the ones that have the right "album description"
console.log("Deleting previously auto-generated albums...");
albums.forEach(async (album, index) => {
if (album.description == albumDescription) {
setTimeout(async function () {
await fetch(immichHost + "/api/album/" + album.id, {
"headers": {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
},
"method": "DELETE",
"mode": "cors",
"credentials": "include"
});
console.log("Deleted album [" + index + "/" + albums.length + "]");
}, index * 250); // Space out each album deletion by 250ms to not hit all at once
}
});
// Get all assets (i.e. photos and videos)
console.log("Fetching all assets (might take some time)...");
let assets = await (await fetch(immichHost + "/api/asset", {
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
})).json();
// Go through all assets and create mapping of "folders" and what assets are in each folder
let albumsWithAssets = {};
assets.forEach(asset => {
if (asset.originalPath) {
let folder = asset.originalPath.replace(rootPath, "").split("/")[0];
if (foldersToExclude.includes(folder) == false) {
if (!albumsWithAssets[folder]) {
albumsWithAssets[folder] = {
assets: []
}
}
albumsWithAssets[folder].assets.push(asset.id);
}
}
});
// For all the "folders" identified, create an immich album for each, adding the respective assets to each
console.log("Creating all albums with their assets...");
Object.keys(albumsWithAssets).forEach(async (folder, index) => {
let body = {
albumName: folder,
description: albumDescription,
assetIds: albumsWithAssets[folder].assets
}
if (index < albumGenerationLimit) { // Limits the number of albums auto-generated, mainly for testing purposes
setTimeout(async function () {
await fetch(immichHost + "/api/album", {
"headers": {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
},
"body": JSON.stringify(body),
"method": "POST",
"mode": "cors",
"credentials": "include"
});
console.log("Created album [" + index + "/" + Object.keys(albumsWithAssets).length + "] for: " + folder);
}, index * 250); // Space out each album creation by 250ms to not hit all at once
}
});
if (albumGenerationLimit < Object.keys(albumsWithAssets).length) {
console.warn("Not all folders will be created. Confirm the albumGenerationLimit parameter was properly specified");
}
})();
@hectorzin
Copy link

It would be good to go into subfolders, meaning that if I have a folder called “2020” and inside it, there are folders named “cruise” and “wedding,” the script should create albums for “cruise” and “wedding,” not just an album named “2020.” The actual album is always within the last-level folder.

Other than that, your script is great! Thank you! 😊

@davidacampos
Copy link
Author

That's a good point. As of now the script creates albums just for folders at the top-level, but it could also be done at the bottom-level instead. The problem would be trying to do this in in-between levels. I'll try to add a flag that specifies if it's a top-level or bottom-level folder to album creation.

@hectorzin
Copy link

There is a bug, you must modify this line
if (asset.originalPath) {
by
if (asset.originalPath && asset.originalPath.includes(rootPath)) {
if not, if you have photos added, or photos in the trash, the internal folder used by immish to store photos that are numbers, are added as albums, you must ensure that the originalPath is inside the external library path.

Also, if you want to modify it to use the last folder nanme in the structrure of folders and not the first one, the change I did is the next one

changed
let folder = asset.originalPath.replace(rootPath, "").split("/")[0];
by
let segments = asset.originalPath.replace(rootPath, "").split("/");
let folder = segments[segments.length - 2];

Thanks for your script.!

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