diff --git a/frontend/src/lib/components/SettingsPanel.svelte b/frontend/src/lib/components/SettingsPanel.svelte index 2345925..b32bea1 100644 --- a/frontend/src/lib/components/SettingsPanel.svelte +++ b/frontend/src/lib/components/SettingsPanel.svelte @@ -78,10 +78,10 @@ month: config.processing.timestamp.month, day: config.processing.timestamp.day, }, - photo_limit: { - enabled: config.processing.photo_limit.enabled, - max_photos: Number(config.processing.photo_limit.max_photos), - time_range: config.processing.photo_limit.time_range, + time_interval: { + enabled: config.processing.time_interval.enabled, + max_photos: Number(config.processing.time_interval.max_photos), + time_range: config.processing.time_interval.time_range, }, }, video: { @@ -413,17 +413,17 @@ - +
- Photo Limit + Time Interval
- {#if config.processing.photo_limit.enabled} + {#if config.processing.time_interval.enabled}
@@ -447,7 +447,7 @@ Time Range Group photos by this period - diff --git a/frontend/src/lib/constants.js b/frontend/src/lib/constants.js index e986d9b..02328fe 100644 --- a/frontend/src/lib/constants.js +++ b/frontend/src/lib/constants.js @@ -166,7 +166,7 @@ export const DEFAULT_CONFIG = { month: false, day: false, }, - photo_limit: { + time_interval: { enabled: false, max_photos: 1, time_range: 'day', diff --git a/src/config.rs b/src/config.rs index c25448b..0e18e90 100644 --- a/src/config.rs +++ b/src/config.rs @@ -455,12 +455,12 @@ pub enum TimeRange { Month, } -/// Photo limit configuration. +/// Time interval configuration. /// /// Limits the number of successfully processed photos per time range /// (day/week/month) to avoid over-representation of busy periods. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PhotoLimitConfig { +pub struct TimeIntervalConfig { /// Whether photo limiting is enabled. pub enabled: bool, @@ -471,7 +471,7 @@ pub struct PhotoLimitConfig { pub time_range: TimeRange, } -impl Default for PhotoLimitConfig { +impl Default for TimeIntervalConfig { fn default() -> Self { Self { enabled: false, @@ -481,17 +481,17 @@ impl Default for PhotoLimitConfig { } } -impl PhotoLimitConfig { +impl TimeIntervalConfig { /// Validate the configuration values. pub fn validate(&self) -> Result<()> { if self.enabled && self.max_photos == 0 { return Err(Error::Config( - "Photo limit max_photos must be greater than 0".to_string(), + "Time interval max_photos must be greater than 0".to_string(), )); } if self.enabled && self.max_photos > 100 { return Err(Error::Config( - "Photo limit max_photos must be at most 100".to_string(), + "Time interval max_photos must be at most 100".to_string(), )); } Ok(()) @@ -544,9 +544,9 @@ pub struct ProcessingConfig { #[serde(default)] pub timestamp: TimestampConfig, - /// Photo limit settings. + /// Time interval settings. #[serde(default)] - pub photo_limit: PhotoLimitConfig, + pub time_interval: TimeIntervalConfig, } impl Default for ProcessingConfig { @@ -561,7 +561,7 @@ impl Default for ProcessingConfig { output: OutputConfig::default(), alignment: AlignmentConfig::default(), timestamp: TimestampConfig::default(), - photo_limit: PhotoLimitConfig::default(), + time_interval: TimeIntervalConfig::default(), } } } @@ -577,7 +577,7 @@ impl ProcessingConfig { self.output.validate()?; self.alignment.validate()?; self.timestamp.validate()?; - self.photo_limit.validate()?; + self.time_interval.validate()?; Ok(()) } } diff --git a/src/job/mod.rs b/src/job/mod.rs index 800f163..63617c2 100644 --- a/src/job/mod.rs +++ b/src/job/mod.rs @@ -8,7 +8,7 @@ mod processing; -use crate::config::{PhotoLimitConfig, TimeRange}; +use crate::config::{TimeIntervalConfig, TimeRange}; use crate::error::{Error, Result}; use crate::immich_api::{Asset, FaceData, ImmichClient}; use crate::pipeline::Pipeline; @@ -30,15 +30,15 @@ use tokio_util::sync::CancellationToken; /// /// Thread-safe: uses atomic counters with fetch_add + undo pattern /// so concurrent workers can claim slots without races. -pub struct PhotoLimitTracker { +pub struct TimeIntervalTracker { max_photos: u32, time_range: TimeRange, buckets: RwLock>>, } -impl PhotoLimitTracker { +impl TimeIntervalTracker { /// Create a tracker if photo limiting is enabled, otherwise return None. - pub fn new(config: &PhotoLimitConfig) -> Option { + pub fn new(config: &TimeIntervalConfig) -> Option { if !config.enabled { return None; } @@ -277,7 +277,7 @@ async fn run_job_inner( tracing::debug!("Pipeline steps: {:?}", pipeline.step_ids()); // Create photo limit tracker if enabled - let photo_limit_tracker = PhotoLimitTracker::new(&config.processing.photo_limit).map(Arc::new); + let time_interval_tracker = TimeIntervalTracker::new(&config.processing.time_interval).map(Arc::new); // Process images in parallel with concurrency limit let completed = Arc::new(AtomicU32::new(0)); @@ -317,7 +317,7 @@ async fn run_job_inner( let skip_stats = skip_stats.clone(); // Clone photo limit tracker for this task - let photo_limit_tracker = photo_limit_tracker.clone(); + let time_interval_tracker = time_interval_tracker.clone(); // Clone person info for this task let person_id = task_person_id.clone(); @@ -341,7 +341,7 @@ async fn run_job_inner( &task_cancel_token, &skip_stats, &pipeline, - photo_limit_tracker.as_ref(), + time_interval_tracker.as_ref(), ) .await; diff --git a/src/job/processing.rs b/src/job/processing.rs index 3684882..51cd93e 100644 --- a/src/job/processing.rs +++ b/src/job/processing.rs @@ -8,7 +8,7 @@ use crate::immich_api::{Asset, FaceData, ImmichClient}; use crate::pipeline::{Pipeline, PipelineContext, PipelineResult}; use crate::web::AtomicSkipStats; -use super::PhotoLimitTracker; +use super::TimeIntervalTracker; use bytes::Bytes; use image::ImageFormat; @@ -70,7 +70,7 @@ pub async fn process_single_asset( cancel_token: &CancellationToken, skip_stats: &Arc, pipeline: &Pipeline, - photo_limit: Option<&Arc>, + time_interval: Option<&Arc>, ) -> AssetProcessResult { let asset_id = &asset.id; @@ -123,12 +123,13 @@ pub async fn process_single_asset( .. } => { // Check photo limit before saving - if let Some(tracker) = photo_limit { + if let Some(tracker) = time_interval { if !tracker.try_claim(×tamp) { - skip_stats.increment("photo_limit"); + tracing::debug!("Asset {} skipped: time interval too short (timestamp: {})", asset_id, timestamp); + skip_stats.increment("time_interval"); return AssetProcessResult::Skipped { asset_id, - reason: "Photo limit reached for time range".to_string(), + reason: "Time interval too short".to_string(), }; } } diff --git a/src/web/handlers/config.rs b/src/web/handlers/config.rs index 6784515..1156533 100644 --- a/src/web/handlers/config.rs +++ b/src/web/handlers/config.rs @@ -2,7 +2,7 @@ use crate::config::{ AlignmentConfig, BlurConfig, BrightnessConfig, EyeFilterConfig, FaceResolutionConfig, - HeadPoseConfig, OutputConfig, PhotoLimitConfig, ProcessingConfig, TimestampConfig, VideoConfig, + HeadPoseConfig, OutputConfig, TimeIntervalConfig, ProcessingConfig, TimestampConfig, VideoConfig, }; use crate::web::state::AppState; use axum::{extract::State, http::StatusCode, response::Json}; @@ -46,7 +46,7 @@ pub struct ProcessingConfigUpdate { pub output: Option, pub alignment: Option, pub timestamp: Option, - pub photo_limit: Option, + pub time_interval: Option, } /// Video configuration update fields. @@ -178,16 +178,16 @@ fn validate_processing_config(proc: &ProcessingConfigUpdate) -> Result<(), Valid } } - if let Some(ref pl) = proc.photo_limit { + if let Some(ref pl) = proc.time_interval { if pl.enabled && pl.max_photos == 0 { return Err(ValidationError::new( - "processing.photo_limit.max_photos", + "processing.time_interval.max_photos", "must be greater than 0 when enabled", )); } if pl.enabled && pl.max_photos > 100 { return Err(ValidationError::new( - "processing.photo_limit.max_photos", + "processing.time_interval.max_photos", format!("must be at most 100, got {}", pl.max_photos), )); } @@ -286,8 +286,8 @@ pub async fn update_config( if let Some(v) = proc.timestamp { config.processing.timestamp = v; } - if let Some(v) = proc.photo_limit { - config.processing.photo_limit = v; + if let Some(v) = proc.time_interval { + config.processing.time_interval = v; } }