immich-tools/src/commands/auto_create_albums.rs

118 lines
3.5 KiB
Rust

use std::path::{Path, PathBuf};
use crate::{
actions::{
action::Action,
add_assets_to_album::{AddAssetsToAlbum, AddAssetsToAlbumArgs},
create_album::{CreateAlbum, CreateAlbumArgs},
fetch_all_albums::FetchAllAlbums,
fetch_all_libraries::FetchAllLibraries,
fetch_library_assets::FetchLibraryAssets,
},
context::Context,
models::{asset::Asset, library::Library},
};
use color_eyre::eyre::Result;
use log::*;
use multimap::MultiMap;
pub async fn auto_create_albums(ctx: Context, separator: String) -> Result<()> {
let albums = FetchAllAlbums::new(()).execute(&ctx).await?;
let libraries = FetchAllLibraries::new(()).execute(&ctx).await?;
let mut assets: Vec<Asset> = vec![];
for library in &libraries {
assets.extend(
FetchLibraryAssets::new(library.clone())
.execute(&ctx)
.await?
.drain(0..),
);
}
let mut sorted_assets = MultiMap::new();
for asset in &assets {
let path = extract_path(asset, &libraries);
if let Some(path) = path {
let asset_albums = extract_album_names(path, separator.clone());
for album in asset_albums {
sorted_assets.insert(album, asset.clone());
}
}
}
debug!("Generated albums: {:?}", sorted_assets.keys());
let missing_albums: Vec<_> = sorted_assets
.keys()
.filter(|k| !albums.iter().any(|album| album.name == **k))
.cloned()
.collect();
info!("Creating missing albums: {:?}", missing_albums);
for missing_album in &missing_albums {
let assets = sorted_assets
.remove(&missing_album.to_string())
.unwrap_or(vec![]);
CreateAlbum::new(CreateAlbumArgs {
name: missing_album.to_string(),
assets,
})
.execute(&ctx)
.await?;
}
for (album, assets) in sorted_assets {
let existing_album = albums
.iter()
.find(|existing_album| existing_album.name == album);
// Albums not present in albums were created just above with all the assets, so it's fine
// to skip. On the other hand, Immich won't re-add the same asset multiple times to the
// same album, so it's safe to re-add.
if let Some(existing_album) = existing_album {
info!("Adding {} assets to {}", assets.len(), album);
AddAssetsToAlbum::new(AddAssetsToAlbumArgs {
album: existing_album.clone(),
assets,
})
.execute(&ctx)
.await?;
}
}
Ok(())
}
fn extract_path(asset: &Asset, libraries: &[Library]) -> Option<PathBuf> {
for library in libraries {
for import_path in &library.import_paths {
if asset.original_path.starts_with(import_path) {
return asset
.original_path
.strip_prefix(import_path)
.ok()
.and_then(Path::parent)
.map(Path::to_path_buf);
}
}
}
None
}
fn extract_album_names(folder_path: PathBuf, separator: String) -> Vec<String> {
let mut components = vec![];
let mut current_component = String::new();
for component in folder_path.components() {
let component_str = component.as_os_str().to_str().unwrap();
current_component += component_str;
components.push(current_component.clone());
current_component += &separator;
}
components
}