Last active
December 31, 2024 18:50
-
-
Save tsvetkovv/114182a59582c0c680fd64f0cd059758 to your computer and use it in GitHub Desktop.
Create Immich albums from the folder structure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Immich Album Creator Script | |
* | |
* This script automatically creates albums in Immich based on your folder structure. | |
* | |
* Expected folder structure: | |
* mnt/originals/{year}/{month}/{album-name} | |
* | |
* Examples of valid paths: | |
* - mnt/originals/2023/05/2023-05-01-Birthday | |
* - mnt/originals/2016/04/2016-04-10-Italy | |
* | |
* The script will: | |
* 1. Only process folders that follow the pattern above | |
* 2. Create an album using the last part of the path as the album name | |
* 3. Add all photos from that folder to the created album | |
* | |
* To modify the folder structure detection: | |
* 1. Find the hasAlbumName() function | |
* 2. Modify the conditions to match your structure | |
* | |
* Example for different folder structure: | |
* For structure like "photos/2023/Family/Greece": | |
* | |
* const hasAlbumName = (path) => { | |
* const parts = path.split('/'); | |
* return parts.length > 3 && parts[0] === 'photos'; | |
* }; | |
* | |
* To modify the album naming: | |
* 1. Find the getAlbumName() function | |
* 2. Adjust the logic to extract the desired album name | |
* | |
* Known limitations: | |
* - Must be run while logged into Immich | |
* - Processes all matching folders at once | |
* | |
* Usage: | |
* 1. Login into Immich web page and open browser developer tools (F12) | |
* 2. Copy and paste this entire script | |
* 3. Press Enter to execute | |
*/ | |
async function createImmichAlbums() { | |
// Helper function to delay between requests | |
const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
// Helper function to extract album name from path | |
const getAlbumName = (path) => { | |
const parts = path.split('/'); | |
return parts[parts.length - 1]; | |
}; | |
// Helper function to check if path contains an album name | |
const hasAlbumName = (path) => { | |
const parts = path.split('/'); | |
// Check both mnt/originals/... and mnt/... patterns | |
if (parts[0] !== 'mnt') return false; | |
if (parts[1] === 'originals') { | |
return parts.length > 4; | |
} | |
return parts.length > 3; | |
}; | |
try { | |
// 1. Fetch existing albums first | |
const existingAlbumsResponse = await fetch('/api/albums'); | |
if (!existingAlbumsResponse.ok) throw new Error('Failed to fetch existing albums'); | |
const existingAlbums = await existingAlbumsResponse.json(); | |
const existingAlbumNames = new Set(existingAlbums.map(album => album.albumName)); | |
console.log(`Found ${existingAlbumNames.size} existing albums`); | |
// 2. Fetch all unique paths | |
const pathsResponse = await fetch('/api/view/folder/unique-paths'); | |
if (!pathsResponse.ok) throw new Error('Failed to fetch paths'); | |
const paths = await pathsResponse.json(); | |
// 3. Filter paths that contain album names | |
const albumPaths = paths.filter(hasAlbumName); | |
console.log(`Found ${albumPaths.length} potential albums to process`); | |
// 4. Process each album path | |
for (const path of albumPaths) { | |
try { | |
const albumName = getAlbumName(path); | |
// Skip if album already exists | |
if (existingAlbumNames.has(albumName)) { | |
console.log(`Skipping "${albumName}" - album already exists`); | |
continue; | |
} | |
// Get photos for this folder | |
const folderResponse = await fetch(`/api/view/folder?path=${encodeURIComponent(path)}`); | |
if (!folderResponse.ok) throw new Error(`Failed to fetch folder contents for ${path}`); | |
const folderContents = await folderResponse.json(); | |
const photoIds = folderContents.map(photo => photo.id); | |
if (photoIds.length === 0) { | |
console.log(`Skipping ${path} - no photos found`); | |
continue; | |
} | |
// Create album | |
const createAlbumResponse = await fetch('/api/albums', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ albumName }) | |
}); | |
if (!createAlbumResponse.ok) throw new Error(`Failed to create album ${albumName}`); | |
const album = await createAlbumResponse.json(); | |
// Add photos to album | |
const addPhotosResponse = await fetch(`/api/albums/${album.id}/assets`, { | |
method: 'PUT', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ ids: photoIds }) | |
}); | |
if (!addPhotosResponse.ok) throw new Error(`Failed to add photos to album ${albumName}`); | |
console.log(`Successfully created album "${albumName}" with ${photoIds.length} photos`); | |
// Add a small delay between operations to avoid overwhelming the server | |
await delay(1000); | |
} catch (error) { | |
console.error(`Error processing path ${path}:`, error); | |
continue; // Continue with next album even if this one fails | |
} | |
} | |
console.log('Album creation process completed!'); | |
} catch (error) { | |
console.error('Failed to process albums:', error); | |
} | |
} | |
createImmichAlbums(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment