Compare commits
8 commits
822c193b3c
...
402d3a473b
Author | SHA1 | Date | |
---|---|---|---|
|
402d3a473b | ||
|
6532bc998d | ||
9df13a9f61 | |||
|
5b703310ba | ||
47c039cc71 | |||
|
7ddc1ef26d | ||
|
9da2855b49 | ||
|
266aa71804 |
7 changed files with 923 additions and 486 deletions
1216
Cargo.lock
generated
1216
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -18,13 +18,13 @@ clap = { version = "4.5.20", features = ["derive"] }
|
||||||
clap-verbosity-flag = "3.0.0"
|
clap-verbosity-flag = "3.0.0"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
dialoguer = "0.11.0"
|
dialoguer = "0.11.0"
|
||||||
directories = "5.0.1"
|
directories = "6.0.0"
|
||||||
figment = { version = "0.10.19", features = ["env", "toml"] }
|
figment = { version = "0.10.19", features = ["env", "toml"] }
|
||||||
figment_file_provider_adapter = "0.1.1"
|
figment_file_provider_adapter = "0.1.1"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
multimap = "0.10.0"
|
multimap = "0.10.0"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
progenitor-client = "0.8.0"
|
progenitor-client = "0.11.0"
|
||||||
readonly = "0.2.12"
|
readonly = "0.2.12"
|
||||||
regress = "0.10.1"
|
regress = "0.10.1"
|
||||||
reqwest = { version = "0.12.8", features = ["json", "stream"] }
|
reqwest = { version = "0.12.8", features = ["json", "stream"] }
|
||||||
|
@ -37,7 +37,7 @@ vcard4 = "0.5.2"
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
openapiv3 = "2.0.0"
|
openapiv3 = "2.0.0"
|
||||||
prettyplease = "0.2.24"
|
prettyplease = "0.2.24"
|
||||||
progenitor = "0.8.0"
|
progenitor = "0.11.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
syn = "2.0"
|
syn = "2.0"
|
||||||
|
|
||||||
|
|
169
README.md
169
README.md
|
@ -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:
|
[](https://crates.io/crates/immich-tools)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
- List named faces that do not have an associated date of birth
|
`immich-tools` is a command-line utility for interacting with [Immich](https://immich.app), a self-hosted photo and
|
||||||
- Synchronise date of births from a vcard file
|
video backup solution. This tool provides various utilities to manage your Immich server from the command line.
|
||||||
- List and delete assets, including filtering offline assets only
|
|
||||||
- Show details about the server
|
|
||||||
|
|
||||||
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
|
## 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
|
- `server_url`: The API endpoint of your Immich instance (usually ends with `/api`)
|
||||||
- `api_key`: the API key to use
|
- `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`
|
1. **Command line arguments**:
|
||||||
- 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`
|
```bash
|
||||||
- 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.
|
immich-tools --server-url https://photos.example.com/api --api-key your-api-key server version
|
||||||
- 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.
|
```
|
||||||
|
|
||||||
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
|
```toml
|
||||||
server_url = "https://photos.example.com/api"
|
server_url = "https://photos.example.com/api"
|
||||||
|
@ -35,6 +86,86 @@ api_key_file = "/home/example/.config/immichtools/apikey"
|
||||||
vcard = "/home/example/contacts.vcf"
|
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)).
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub async fn auto_create_albums(ctx: Context, separator: String) -> Result<()> {
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
info!("Creating missing albums: {:?}", missing_albums);
|
info!("Creating missing albums: {missing_albums:?}");
|
||||||
|
|
||||||
for missing_album in &missing_albums {
|
for missing_album in &missing_albums {
|
||||||
let assets = sorted_assets
|
let assets = sorted_assets
|
||||||
|
|
|
@ -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())?;
|
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) {
|
if c.date_of_birth.as_ref().is_some_and(|bdate| bday == *bdate) {
|
||||||
debug!(
|
debug!(
|
||||||
"{} already has the proper date of birth, skipping",
|
"{formatted_name} already has the proper date of birth, skipping"
|
||||||
formatted_name
|
|
||||||
);
|
);
|
||||||
} else if bday.year() > 0 {
|
} else if bday.year() > 0 {
|
||||||
UpdatePersonDateOfBirth::new(UpdatePersonDateOfBirthArgs {
|
UpdatePersonDateOfBirth::new(UpdatePersonDateOfBirthArgs {
|
||||||
|
@ -68,8 +67,7 @@ pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()
|
||||||
updated_dobs += 1;
|
updated_dobs += 1;
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
"{} has an incomplete date of birth, skipping",
|
"{formatted_name} has an incomplete date of birth, skipping"
|
||||||
formatted_name
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,14 +75,14 @@ pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()
|
||||||
todo!("{}: DateTimeOrTextProperty({})", formatted_name, prop)
|
todo!("{}: DateTimeOrTextProperty({})", formatted_name, prop)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => debug!("No Immich match for {}", formatted_name),
|
None => debug!("No Immich match for {formatted_name}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => todo!(),
|
None => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Updated {} persons", updated_dobs);
|
info!("Updated {updated_dobs} persons");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ async fn main() -> Result<()> {
|
||||||
if let Some(project_dirs) = ProjectDirs::from("fr", "enoent", "Immich Tools") {
|
if let Some(project_dirs) = ProjectDirs::from("fr", "enoent", "Immich Tools") {
|
||||||
let config_file_path = project_dirs.config_dir().join("config.toml");
|
let config_file_path = project_dirs.config_dir().join("config.toml");
|
||||||
if config_file_path.exists() {
|
if config_file_path.exists() {
|
||||||
debug!("Reading configuration from {:?}", config_file_path);
|
debug!("Reading configuration from {config_file_path:?}");
|
||||||
conf_extractor =
|
conf_extractor =
|
||||||
conf_extractor.merge(FileAdapter::wrap(Toml::file_exact(config_file_path)));
|
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(|| {
|
.with_note(|| {
|
||||||
format!(
|
format!(
|
||||||
"The API version for this client is {}, make sure the server version is compatible",
|
"The API version for this client is {}, make sure the server version is compatible",
|
||||||
client.api_version()
|
Client::api_version()
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub struct AssetQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_all_assets(query: AssetQuery, client: &Client) -> Result<Vec<AssetResponseDto>> {
|
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
|
// is_offline is ignored, let's fetch trashed assets instead and filter them later
|
||||||
let trashed_after = if query.is_offline == Some(true) {
|
let trashed_after = if query.is_offline == Some(true) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue