Compare commits

..

8 commits

Author SHA1 Message Date
Renovate
402d3a473b fix(deps): update rust crate clap-verbosity-flag to v3
Some checks failed
Checking Renovate configuration / validate (pull_request) Successful in 2m21s
Build and test / Clippy (pull_request) Successful in 3m23s
Build and test / Tests (pull_request) Successful in 3m25s
Build and test / Build AMD64 (pull_request) Successful in 3m22s
Checking yaml / Run yamllint (pull_request) Failing after 14m11s
Build and test / Generate Documentation (pull_request) Failing after 15m16s
2025-08-03 15:30:24 +10:00
Renovate
6532bc998d fix(deps): update rust crate tokio to v1.47.1
All checks were successful
Checking Renovate configuration / validate (pull_request) Successful in 1m13s
Build and test / Clippy (pull_request) Successful in 1m25s
Build and test / Tests (pull_request) Successful in 1m27s
Build and test / Build AMD64 (pull_request) Successful in 1m24s
Checking yaml / Run yamllint (pull_request) Successful in 3s
Build and test / Generate Documentation (pull_request) Successful in 1m10s
2025-08-01 21:30:26 +10:00
9df13a9f61
chore: update README
All checks were successful
Checking Renovate configuration / validate (pull_request) Successful in 1m25s
Build and test / Clippy (pull_request) Successful in 2m5s
Checking yaml / Run yamllint (pull_request) Successful in 3s
Build and test / Tests (pull_request) Successful in 2m13s
Build and test / Build AMD64 (pull_request) Successful in 2m10s
Build and test / Generate Documentation (pull_request) Successful in 1m14s
2025-08-01 15:49:23 +10:00
Renovate
5b703310ba chore(deps): lock file maintenance
All checks were successful
Build and test / Clippy (pull_request) Successful in 1m59s
Build and test / Build AMD64 (pull_request) Successful in 1m57s
Build and test / Generate Documentation (pull_request) Successful in 1m39s
Checking Renovate configuration / validate (pull_request) Successful in 1m50s
Checking yaml / Run yamllint (pull_request) Successful in 12s
Build and test / Tests (pull_request) Successful in 1m40s
2025-08-01 15:06:51 +10:00
47c039cc71
chore(deps): update rust crate progenitor to 0.11.0
All checks were successful
Checking Renovate configuration / validate (pull_request) Successful in 1m37s
Build and test / Tests (pull_request) Successful in 1m52s
Build and test / Clippy (pull_request) Successful in 1m52s
Checking yaml / Run yamllint (pull_request) Successful in 3s
Build and test / Build AMD64 (pull_request) Successful in 1m53s
Build and test / Generate Documentation (pull_request) Successful in 1m53s
2025-08-01 14:58:17 +10:00
Renovate
7ddc1ef26d fix(deps): update rust crate uuid to v1.17.0
All checks were successful
Checking Renovate configuration / validate (pull_request) Successful in 1m17s
Build and test / Clippy (pull_request) Successful in 1m32s
Build and test / Tests (pull_request) Successful in 1m37s
Build and test / Build AMD64 (pull_request) Successful in 1m34s
Checking yaml / Run yamllint (pull_request) Successful in 3s
Build and test / Generate Documentation (pull_request) Successful in 1m5s
2025-08-01 14:39:19 +10:00
Renovate
9da2855b49 fix(deps): update rust crate directories to v6
All checks were successful
Build and test / Clippy (pull_request) Successful in 1m20s
Checking Renovate configuration / validate (pull_request) Successful in 1m9s
Build and test / Generate Documentation (pull_request) Successful in 1m5s
Build and test / Tests (pull_request) Successful in 1m25s
Build and test / Build AMD64 (pull_request) Successful in 1m21s
Checking yaml / Run yamllint (pull_request) Successful in 3s
2025-08-01 14:36:42 +10:00
Renovate
266aa71804 chore(deps): update rust crate openapiv3 to v2.2.0
All checks were successful
Checking Renovate configuration / validate (pull_request) Successful in 1m20s
Checking yaml / Run yamllint (pull_request) Successful in 5s
Build and test / Generate Documentation (pull_request) Successful in 1m17s
Build and test / Clippy (pull_request) Successful in 1m21s
Build and test / Tests (pull_request) Successful in 1m34s
Build and test / Build AMD64 (pull_request) Successful in 55s
2025-08-01 14:13:41 +10:00
7 changed files with 923 additions and 486 deletions

1216
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,13 +18,13 @@ clap = { version = "4.5.20", features = ["derive"] }
clap-verbosity-flag = "3.0.0"
color-eyre = "0.6.3"
dialoguer = "0.11.0"
directories = "5.0.1"
directories = "6.0.0"
figment = { version = "0.10.19", features = ["env", "toml"] }
figment_file_provider_adapter = "0.1.1"
log = "0.4.22"
multimap = "0.10.0"
pretty_env_logger = "0.5.0"
progenitor-client = "0.8.0"
progenitor-client = "0.11.0"
readonly = "0.2.12"
regress = "0.10.1"
reqwest = { version = "0.12.8", features = ["json", "stream"] }
@ -37,7 +37,7 @@ vcard4 = "0.5.2"
[build-dependencies]
openapiv3 = "2.0.0"
prettyplease = "0.2.24"
progenitor = "0.8.0"
progenitor = "0.11.0"
serde_json = "1.0"
syn = "2.0"

169
README.md
View file

@ -1,31 +1,82 @@
# Immich CLI utilities
# Immich Tools
`immich-tools` is a small command-line utility interacting with an Immich server. It offers the following features:
[![Crates.io](https://img.shields.io/crates/v/immich-tools.svg)](https://crates.io/crates/immich-tools)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
- List named faces that do not have an associated date of birth
- Synchronise date of births from a vcard file
- List and delete assets, including filtering offline assets only
- Show details about the server
`immich-tools` is a command-line utility for interacting with [Immich](https://immich.app), a self-hosted photo and
video backup solution. This tool provides various utilities to manage your Immich server from the command line.
Some other features are planned, mainly around using external libraries. Feature ideas and contributions welcome.
## Features
### Albums
- **List** all albums on your Immich server
- **Delete** albums (all or empty ones only)
- **Auto-create** albums from external libraries folder structure
### Assets
- **List** assets (all or offline only)
- **Delete** assets (all or offline only)
### External libraries
- **List** all libraries
- **Scan** all libraries to detect new assets
### People
- **List** named faces that do not have an associated date of birth
- **Synchronize** dates of birth from a vCard file
### Server
- **Show** server version
- **Check** which server features are enabled
## Installation
### From Crates.io
```bash
cargo install immich-tools
```
### From Source
```bash
git clone https://git.enoent.fr/kernald/immich-tools.git
cd immich-tools
cargo build --release
```
The binary will be available at `./target/release/immich-tools`.
## Configuration
Different options need to be passed to this tool, mainly:
To connect to your Immich server, you need to provide:
- `server_url`: the API endpoint of the Immich instance to work on
- `api_key`: the API key to use
- `server_url`: The API endpoint of your Immich instance (usually ends with `/api`)
- `api_key`: Your Immich API key (can be generated in the Immich web interface)
These can be passed through different mechanisms:
These can be provided in several ways (in order of precedence):
- Command line arguments, e.g. `immich-tools --server-url https://photos.example.com/api --api-key api-key-goes-here server version`
- Environment variables prefixed with `IMMICH_TOOLS_`, e.g. `IMMICH_TOOLS_SERVER_URL=https://photos.example.com/api IMMICH_TOOLS_API_KEY=api-key-goes-here immich-tools server version`
- Environment variables pointing to files, suffixed with `_FILE`, e.g. `IMMICHTOOLS_SERVER_URL_FILE=~/immich-url.txt IMMICH_TOOLS_API_KEY_FILE=~/immich-api-key.txt immich-tools server version`. This is mostly useful for secrets.
- Through a configuration file, in a well-known, OS-dependent location (on Linux, `~/.config/immichtools/config.toml`, on macOS, `~/Library/Application Support/fr.enoent.Immich-Tools/config.toml`, and on Windows `~\AppData\Roaming\enoent\Immich Tools\config\config.toml`). Note that keys in this file can also be suffixed with `_file` and point to a file containing the value, instead of the value directly.
1. **Command line arguments**:
```bash
immich-tools --server-url https://photos.example.com/api --api-key your-api-key server version
```
Command line arguments take precedence over environment variables, which in turn take precedence over the configuration file.
2. **Environment variables** (prefixed with `IMMICH_TOOLS_`):
```bash
IMMICH_TOOLS_SERVER_URL=https://photos.example.com/api IMMICH_TOOLS_API_KEY=your-api-key immich-tools server version
```
### Example configuration file
3. **Environment variables pointing to files** (suffixed with `_FILE`):
```bash
IMMICH_TOOLS_SERVER_URL_FILE=~/immich-url.txt IMMICH_TOOLS_API_KEY_FILE=~/immich-api-key.txt immich-tools server version
```
4. **Configuration file** in the OS-specific location:
- Linux: `~/.config/immichtools/config.toml`
- macOS: `~/Library/Application Support/fr.enoent.Immich-Tools/config.toml`
- Windows: `~\AppData\Roaming\enoent\Immich Tools\config\config.toml`
### Example Configuration File
```toml
server_url = "https://photos.example.com/api"
@ -35,6 +86,86 @@ api_key_file = "/home/example/.config/immichtools/apikey"
vcard = "/home/example/contacts.vcf"
```
## Immich API bindings
## Usage Examples
The bindings are generated automatically from `src/immich-openapi-specs.json`. The file currently in the repo has been generated with Immich v1.119.1, with a few methods removed around file upload (Progenitor doesn't yet support `multipart/form-data` content types, see [here](https://github.com/oxidecomputer/progenitor/issues/518)). All other methods should be available.
### Albums
List all albums:
```bash
immich-tools albums list
```
Delete empty albums:
```bash
immich-tools albums delete --empty
```
Auto-create albums from folder structure:
```bash
immich-tools albums auto-create --album-name-separator "/"
```
### Assets
List all assets:
```bash
immich-tools assets list
```
List offline assets only:
```bash
immich-tools assets list --offline
```
Delete offline assets:
```bash
immich-tools assets delete --offline
```
### External libraries
List all libraries:
```bash
immich-tools libraries list
```
Scan all libraries for new assets:
```bash
immich-tools libraries scan
```
### People
List people missing date of birth:
```bash
immich-tools people missing-date-of-births
```
Sync dates of birth from vCard file:
```bash
immich-tools people sync-date-of-births --vcard /path/to/contacts.vcf
```
### Server
Show server version:
```bash
immich-tools server version
```
Check enabled server features:
```bash
immich-tools server features
```
## Global Options
- `--dry-run`: Show what would be done without making any changes
- `--no-confirm`: Skip confirmation prompts
- `-v, --verbose`: Increase logging verbosity (can be used multiple times)
## Immich API Compatibility
The bindings are generated automatically from the Immich OpenAPI specification. The current version is compatible with
Immich v1.119.1, with some limitations around file upload operations (Progenitor doesn't yet support
`multipart/form-data` content types, see [this issue](https://github.com/oxidecomputer/progenitor/issues/518)).

View file

@ -50,7 +50,7 @@ pub async fn auto_create_albums(ctx: Context, separator: String) -> Result<()> {
.cloned()
.collect();
info!("Creating missing albums: {:?}", missing_albums);
info!("Creating missing albums: {missing_albums:?}");
for missing_album in &missing_albums {
let assets = sorted_assets

View file

@ -55,8 +55,7 @@ pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()
let bday = vcard_date_to_naive_date(dt.value.first().unwrap().clone())?;
if c.date_of_birth.as_ref().is_some_and(|bdate| bday == *bdate) {
debug!(
"{} already has the proper date of birth, skipping",
formatted_name
"{formatted_name} already has the proper date of birth, skipping"
);
} else if bday.year() > 0 {
UpdatePersonDateOfBirth::new(UpdatePersonDateOfBirthArgs {
@ -68,8 +67,7 @@ pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()
updated_dobs += 1;
} else {
debug!(
"{} has an incomplete date of birth, skipping",
formatted_name
"{formatted_name} has an incomplete date of birth, skipping"
);
}
}
@ -77,14 +75,14 @@ pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()
todo!("{}: DateTimeOrTextProperty({})", formatted_name, prop)
}
},
None => debug!("No Immich match for {}", formatted_name),
None => debug!("No Immich match for {formatted_name}"),
}
}
None => todo!(),
}
}
info!("Updated {} persons", updated_dobs);
info!("Updated {updated_dobs} persons");
Ok(())
}

View file

@ -55,7 +55,7 @@ async fn main() -> Result<()> {
if let Some(project_dirs) = ProjectDirs::from("fr", "enoent", "Immich Tools") {
let config_file_path = project_dirs.config_dir().join("config.toml");
if config_file_path.exists() {
debug!("Reading configuration from {:?}", config_file_path);
debug!("Reading configuration from {config_file_path:?}");
conf_extractor =
conf_extractor.merge(FileAdapter::wrap(Toml::file_exact(config_file_path)));
}
@ -135,7 +135,7 @@ async fn validate_client_connection(client: &Client) -> Result<()> {
.with_note(|| {
format!(
"The API version for this client is {}, make sure the server version is compatible",
client.api_version()
Client::api_version()
)
})?;

View file

@ -15,7 +15,7 @@ pub struct AssetQuery {
}
pub async fn fetch_all_assets(query: AssetQuery, client: &Client) -> Result<Vec<AssetResponseDto>> {
debug!("Fetching assets with query {:?}", query);
debug!("Fetching assets with query {query:?}");
// is_offline is ignored, let's fetch trashed assets instead and filter them later
let trashed_after = if query.is_offline == Some(true) {