diff --git a/frontend/src/lib/components/SettingsPanel.svelte b/frontend/src/lib/components/SettingsPanel.svelte index 2fa64fc..0a2c71f 100644 --- a/frontend/src/lib/components/SettingsPanel.svelte +++ b/frontend/src/lib/components/SettingsPanel.svelte @@ -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 @@ {config.processing.max_workers} + +
+ + +
diff --git a/frontend/src/lib/constants.js b/frontend/src/lib/constants.js index 6b5cb42..5718cd6 100644 --- a/frontend/src/lib/constants.js +++ b/frontend/src/lib/constants.js @@ -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, diff --git a/src/config.rs b/src/config.rs index 6431506..0c8a318 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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()) diff --git a/src/immich_api/mod.rs b/src/immich_api/mod.rs index 92a2e39..ee295c1 100644 --- a/src/immich_api/mod.rs +++ b/src/immich_api/mod.rs @@ -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 { + 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> { let url = format!("{}/people", self.base_url); diff --git a/src/job/processing.rs b/src/job/processing.rs index 5095fe7..22dab1e 100644 --- a/src/job/processing.rs +++ b/src/job/processing.rs @@ -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");