Display mode: crop to fill, crop to faces, stretch to fill, shrink to fit
Build and push server image / build-and-push (push) Successful in 40s

Replaces the smart_crop_faces boolean with a 4-way display_mode select
on each frame's Configuration tab (image_pipeline.DISPLAY_MODES):

- Crop to fill / Crop to faces: the previous False/True behavior,
  unchanged (center-crop trimming excess, optionally shifted to keep
  faces on screen).
- Stretch to fill (new): fills the panel exactly, aspect ratio not
  preserved -- a plain resize, no crop.
- Shrink to fit (new): the whole photo visible, letterboxed with white
  where it doesn't fill the panel.

Named-face overlay label positioning (face_labels.py, the manage menu's
"who's in this photo") now goes through a shared _placement_transform()
in image_pipeline.py instead of duplicating crop-box math, so label
placement stays correct (and in-bounds) under all four modes, not just
the two crop ones -- letterbox/stretch never crop a face out, so labels
just use straight scale+offset math there.

Schema migration v5 adds display_mode, backfills it from the old
boolean (True/False -> crop_faces/crop_fill), and drops the boolean.

Verified against the live-shaped test database: the migration
(existing frames correctly preserved as crop_faces), the config page's
new 4-option select, actual renders under letterbox (confirmed real
white letterbox padding in the packed panel-code bytes) and
stretch_fill, invalid-input fallback, and the standing legacy-device
curl suite.
This commit is contained in:
2026-07-22 01:32:52 -04:00
parent e802882fc1
commit 49bc9f9ec9
10 changed files with 125 additions and 46 deletions
+13 -1
View File
@@ -59,11 +59,23 @@ def _migration_4(conn) -> None:
conn.execute(text("ALTER TABLE frames ADD COLUMN palette_rgb TEXT"))
def _migration_5(conn) -> None:
"""Replaces the smart_crop_faces boolean with display_mode (see
image_pipeline.DISPLAY_MODES) -- crop_faces/crop_fill are exactly
the old True/False behavior, stretch_fill/letterbox are new."""
conn.execute(text("ALTER TABLE frames ADD COLUMN display_mode TEXT NOT NULL DEFAULT 'crop_faces'"))
conn.execute(text(
"UPDATE frames SET display_mode = CASE WHEN smart_crop_faces THEN 'crop_faces' ELSE 'crop_fill' END"
))
conn.execute(text("ALTER TABLE frames DROP COLUMN smart_crop_faces"))
MIGRATIONS = [
(1, _migration_1),
(2, _migration_2),
(3, _migration_3),
(4, _migration_4),
(5, _migration_5),
]
@@ -126,7 +138,7 @@ def _ensure_frame_one() -> None:
quiet_hours_start=cfg.quiet_hours_start,
quiet_hours_end=cfg.quiet_hours_end,
timezone=cfg.timezone,
smart_crop_faces=cfg.smart_crop_faces,
display_mode="crop_faces" if cfg.smart_crop_faces else "crop_fill",
orientation=cfg.orientation,
queue_target_len=cfg.queue_target_len,
current_asset_id=cfg.current_asset_id,