Fixed an issue with caching. Initial resize is tied to eye distance setting.
This commit is contained in:
parent
250cbd491a
commit
4f4f64aab1
7 changed files with 24 additions and 33 deletions
|
|
@ -14,6 +14,7 @@
|
|||
let deleting = $state(false);
|
||||
let compiling = $state(false);
|
||||
let videoExists = $state(false);
|
||||
let cacheBust = $state(Date.now());
|
||||
|
||||
let selectedCount = $derived(selectedImages.size);
|
||||
let allSelected = $derived(images.length > 0 && selectedImages.size === images.length);
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
async function loadImages() {
|
||||
loading = true;
|
||||
error = null;
|
||||
cacheBust = Date.now();
|
||||
try {
|
||||
const res = await fetch(`/api/output/${encodeURIComponent(folderName)}/images`);
|
||||
if (!res.ok) {
|
||||
|
|
@ -197,7 +199,7 @@
|
|||
<span class="checkbox">{selectedImages.has(image.filename) ? '✓' : ''}</span>
|
||||
</div>
|
||||
<img
|
||||
src="/output/{encodeURIComponent(folderName)}/images/{encodeURIComponent(image.filename)}"
|
||||
src="/output/{encodeURIComponent(folderName)}/images/{encodeURIComponent(image.filename)}?v={cacheBust}"
|
||||
alt={image.filename}
|
||||
loading="lazy"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
let { folderName = null } = $props();
|
||||
|
||||
let cacheBust = $state(Date.now());
|
||||
let videoUrl = $derived(
|
||||
folderName
|
||||
? `/output/${encodeURIComponent(folderName)}/${encodeURIComponent(folderName)}.mp4`
|
||||
? `/output/${encodeURIComponent(folderName)}/${encodeURIComponent(folderName)}.mp4?v=${cacheBust}`
|
||||
: null
|
||||
);
|
||||
let videoError = $state(false);
|
||||
|
|
|
|||
|
|
@ -316,9 +316,7 @@ pub struct AlignmentConfig {
|
|||
|
||||
impl Default for AlignmentConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
eye_distance: 0.3,
|
||||
}
|
||||
Self { eye_distance: 0.3 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,8 +339,7 @@ impl AlignmentConfig {
|
|||
}
|
||||
if self.eye_distance < 0.1 || self.eye_distance > 0.5 {
|
||||
return Err(Error::Config(
|
||||
"Alignment eye_distance should be between 0.1 and 0.5 for best results"
|
||||
.to_string(),
|
||||
"Alignment eye_distance should be between 0.1 and 0.5 for best results".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub fn crop_face_with_intermediate(
|
|||
img: &DynamicImage,
|
||||
face_data: &FaceData,
|
||||
output_size: u32,
|
||||
eye_distance: f32,
|
||||
) -> Result<CropResult> {
|
||||
let (img_width, img_height) = img.dimensions();
|
||||
|
||||
|
|
@ -50,20 +51,20 @@ pub fn crop_face_with_intermediate(
|
|||
));
|
||||
}
|
||||
|
||||
// Expand the crop area to include some context around the face
|
||||
// and make it square for consistent output
|
||||
let face_size = face_width.max(face_height);
|
||||
let padding = face_size / 2; // 50% padding on each side
|
||||
let crop_size = face_size + padding * 2;
|
||||
// Derive crop size from eye_distance so the initial crop captures
|
||||
// the right amount of context for the alignment step.
|
||||
const IPD_RATIO: f32 = 0.37;
|
||||
const SAFETY_MARGIN: f32 = 1.30;
|
||||
|
||||
let face_size = face_width.max(face_height) as f32;
|
||||
let estimated_ipd = IPD_RATIO * face_width as f32;
|
||||
let ideal_crop = (estimated_ipd / eye_distance) * SAFETY_MARGIN;
|
||||
let crop_size = ideal_crop.max(face_size * 1.5).max(10.0).round() as u32;
|
||||
|
||||
// Calculate center of face
|
||||
let center_x = (x1 + x2) / 2;
|
||||
let center_y = (y1 + y2) / 2;
|
||||
|
||||
if crop_size < 10 {
|
||||
return Err(Error::ImageProcessing("Crop area too small".to_string()));
|
||||
}
|
||||
|
||||
// Ideal crop region centered on the face (may extend outside image bounds)
|
||||
let ideal_x1 = center_x as i32 - crop_size as i32 / 2;
|
||||
let ideal_y1 = center_y as i32 - crop_size as i32 / 2;
|
||||
|
|
|
|||
|
|
@ -73,15 +73,6 @@ impl ProcessingStep for AlignmentStep {
|
|||
|
||||
ctx.image = Some(aligned);
|
||||
|
||||
tracing::trace!(
|
||||
"Aligned: left_eye=({:.1},{:.1}), right_eye=({:.1},{:.1}), target_size={}",
|
||||
left_eye.x,
|
||||
left_eye.y,
|
||||
right_eye.x,
|
||||
right_eye.y,
|
||||
output_size
|
||||
);
|
||||
|
||||
StepOutcome::Continue(ctx)
|
||||
}
|
||||
}
|
||||
|
|
@ -178,10 +169,7 @@ fn calculate_eye_alignment_transform(
|
|||
let left_eye_x = alignment_config.left_eye_x();
|
||||
let left_eye_y = alignment_config.left_eye_y();
|
||||
|
||||
let left_eye_target = Point::new(
|
||||
output_size_f * left_eye_x,
|
||||
output_size_f * left_eye_y,
|
||||
);
|
||||
let left_eye_target = Point::new(output_size_f * left_eye_x, output_size_f * left_eye_y);
|
||||
let right_eye_target = Point::new(
|
||||
output_size_f * (1.0 - left_eye_x),
|
||||
output_size_f * left_eye_y,
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ impl ProcessingStep for CropAndResizeStep {
|
|||
};
|
||||
|
||||
let output_size = config.processing.output.size;
|
||||
let eye_distance = config.processing.alignment.eye_distance;
|
||||
|
||||
// Crop returns CropResult with cropped images and face rectangle in crop coordinates
|
||||
match crop_face_with_intermediate(image, &ctx.face_data, output_size) {
|
||||
match crop_face_with_intermediate(image, &ctx.face_data, output_size, eye_distance) {
|
||||
Ok(crop_result) => {
|
||||
// Use the pre-resized image from the crop function
|
||||
let cropped_size = crop_result.cropped.width();
|
||||
|
|
|
|||
|
|
@ -95,13 +95,14 @@ pub fn create_router(state: AppState) -> Router {
|
|||
)
|
||||
.route("/api/config", get(get_config))
|
||||
.route("/api/config", put(update_config))
|
||||
// Serve output files (video, images) with cache headers
|
||||
// Serve output files (video, images) — no-cache so browsers revalidate
|
||||
// after reprocessing (filenames stay the same across runs)
|
||||
.nest_service(
|
||||
"/output",
|
||||
ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
.layer(SetResponseHeaderLayer::overriding(
|
||||
header::CACHE_CONTROL,
|
||||
header::HeaderValue::from_static("public, max-age=86400"),
|
||||
header::HeaderValue::from_static("no-store"),
|
||||
))
|
||||
.service(ServeDir::new(output_dir)),
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue