Rename date limit setting
This commit is contained in:
parent
0ae09da074
commit
66e7af28eb
6 changed files with 42 additions and 41 deletions
|
|
@ -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 @@
|
|||
<input id="keep-intermediates" type="checkbox" bind:checked={config.processing.output.keep_intermediates} />
|
||||
</div>
|
||||
|
||||
<!-- Photo Limit Section -->
|
||||
<!-- Time Interval Section -->
|
||||
<div class="setting-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">Photo Limit</span>
|
||||
<span class="section-title">Time Interval</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={config.processing.photo_limit.enabled}
|
||||
bind:checked={config.processing.time_interval.enabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if config.processing.photo_limit.enabled}
|
||||
{#if config.processing.time_interval.enabled}
|
||||
<div class="setting-row sub-setting">
|
||||
<label for="max-photos">
|
||||
<span class="setting-label">Max Photos</span>
|
||||
|
|
@ -433,12 +433,12 @@
|
|||
<input
|
||||
id="max-photos"
|
||||
type="range"
|
||||
bind:value={config.processing.photo_limit.max_photos}
|
||||
bind:value={config.processing.time_interval.max_photos}
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
/>
|
||||
<span class="value">{config.processing.photo_limit.max_photos}</span>
|
||||
<span class="value">{config.processing.time_interval.max_photos}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -447,7 +447,7 @@
|
|||
<span class="setting-label">Time Range</span>
|
||||
<span class="setting-hint">Group photos by this period</span>
|
||||
</label>
|
||||
<select id="time-range" bind:value={config.processing.photo_limit.time_range}>
|
||||
<select id="time-range" bind:value={config.processing.time_interval.time_range}>
|
||||
<option value="day">Day</option>
|
||||
<option value="week">Week</option>
|
||||
<option value="month">Month</option>
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ export const DEFAULT_CONFIG = {
|
|||
month: false,
|
||||
day: false,
|
||||
},
|
||||
photo_limit: {
|
||||
time_interval: {
|
||||
enabled: false,
|
||||
max_photos: 1,
|
||||
time_range: 'day',
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HashMap<String, Arc<AtomicU32>>>,
|
||||
}
|
||||
|
||||
impl PhotoLimitTracker {
|
||||
impl TimeIntervalTracker {
|
||||
/// Create a tracker if photo limiting is enabled, otherwise return None.
|
||||
pub fn new(config: &PhotoLimitConfig) -> Option<Self> {
|
||||
pub fn new(config: &TimeIntervalConfig) -> Option<Self> {
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<AtomicSkipStats>,
|
||||
pipeline: &Pipeline,
|
||||
photo_limit: Option<&Arc<PhotoLimitTracker>>,
|
||||
time_interval: Option<&Arc<TimeIntervalTracker>>,
|
||||
) -> 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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OutputConfig>,
|
||||
pub alignment: Option<AlignmentConfig>,
|
||||
pub timestamp: Option<TimestampConfig>,
|
||||
pub photo_limit: Option<PhotoLimitConfig>,
|
||||
pub time_interval: Option<TimeIntervalConfig>,
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue