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");