Add an option to use immich previews (1440p) intead of full images

This commit is contained in:
Arnaud_Cayrol 2026-02-13 17:10:24 +01:00
parent bbe6cd4d01
commit 88214ca413
5 changed files with 55 additions and 2 deletions

View file

@ -41,6 +41,7 @@
const configToSend = {
processing: {
max_workers: Number(config.processing.max_workers),
use_preview: config.processing.use_preview,
face_resolution: {
enabled: config.processing.face_resolution.enabled,
min_size: Number(config.processing.face_resolution.min_size),
@ -384,6 +385,14 @@
<span class="value">{config.processing.max_workers}</span>
</div>
</div>
<div class="setting-row checkbox-row">
<label for="use-preview">
<span class="setting-label">Use Preview Images</span>
<span class="setting-hint">Download Immich 1440p previews instead of originals (much faster)</span>
</label>
<input id="use-preview" type="checkbox" bind:checked={config.processing.use_preview} />
</div>
</div>
<!-- Time Interval Section -->

View file

@ -50,6 +50,7 @@ export const JOB_STATUS = {
export const DEFAULT_CONFIG = {
processing: {
max_workers: 4,
use_preview: true,
face_resolution: {
enabled: true,
min_size: 80,

View file

@ -495,6 +495,12 @@ pub struct ProcessingConfig {
/// Number of parallel workers for processing.
pub max_workers: usize,
/// Whether to download Immich preview images (1440p JPEG) instead of originals.
/// Preview images are much faster to decode and sufficient for most use cases.
/// Disable only if you need full original resolution (e.g., very small faces in high-res photos).
#[serde(default = "default_true")]
pub use_preview: bool,
/// Face resolution validation settings.
#[serde(default)]
pub face_resolution: FaceResolutionConfig,
@ -536,6 +542,7 @@ impl Default for ProcessingConfig {
fn default() -> Self {
Self {
max_workers: num_cpus(),
use_preview: true,
face_resolution: FaceResolutionConfig::default(),
blur: BlurConfig::default(),
brightness: BrightnessConfig::default(),
@ -570,6 +577,10 @@ impl ProcessingConfig {
}
}
fn default_true() -> bool {
true
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|p| p.get())

View file

@ -240,6 +240,33 @@ impl ImmichClient {
Ok(bytes)
}
/// Download an asset's preview image (1440p JPEG pre-generated by Immich).
///
/// Much faster to download and decode than the original.
/// Falls back to the original if the preview is unavailable.
pub async fn download_asset_preview(&self, asset_id: &str) -> Result<bytes::Bytes> {
let encoded_id = urlencode(asset_id);
let url = format!("{}/assets/{}/thumbnail?size=preview", self.base_url, encoded_id);
let response = self
.client
.get(&url)
.header("x-api-key", &self.api_key)
.send()
.await?;
if !response.status().is_success() {
return Err(Error::ImmichApi(format!(
"Failed to download preview for asset {}: {}",
asset_id,
response.status()
)));
}
let bytes = response.bytes().await?;
Ok(bytes)
}
/// Get all people from Immich.
pub async fn get_people(&self) -> Result<Vec<Person>> {
let url = format!("{}/people", self.base_url);

View file

@ -108,8 +108,13 @@ pub async fn process_single_asset(
};
}
// Download image
let image_bytes: Bytes = match client.download_asset(asset_id).await {
// Download image (preview or original based on config)
let download_result = if config.processing.use_preview {
client.download_asset_preview(asset_id).await
} else {
client.download_asset(asset_id).await
};
let image_bytes: Bytes = match download_result {
Ok(bytes) => bytes,
Err(e) => {
skip_stats.increment("download_failed");