Preftified the webpage

This commit is contained in:
Arnaud_Cayrol 2025-04-06 20:45:00 +02:00
parent 50c556e8b0
commit e926625878
3 changed files with 140 additions and 79 deletions

42
main.py
View file

@ -1,14 +1,17 @@
# main.py
import os import os
from flask import Flask, request, render_template from flask import Flask, request, render_template
from timelapse import process_faces from timelapse import process_faces
app = Flask(__name__) app = Flask(__name__)
# Read critical parameters from environment variables # Critical parameters provided via environment variables
DEFAULT_API_KEY = os.environ.get("IMMICH_API_KEY", "") API_KEY = os.environ.get("IMMICH_API_KEY", "")
DEFAULT_BASE_URL = os.environ.get("IMMICH_BASE_URL", "") BASE_URL = os.environ.get("IMMICH_BASE_URL", "")
DEFAULT_OUTPUT_FOLDER = os.environ.get("IMMICH_OUTPUT_FOLDER", "output") OUTPUT_FOLDER = os.environ.get("OUTPUT_FOLDER", "output")
# model paths
FACE_DETECT_MODEL = "mmod_human_face_detector.dat"
LANDMARK_MODEL = "shape_predictor_68_face_landmarks.dat"
@app.route("/", methods=["GET", "POST"]) @app.route("/", methods=["GET", "POST"])
@ -17,24 +20,17 @@ def index():
error = None error = None
if request.method == "POST": if request.method == "POST":
try: try:
# Use environment variable defaults if form fields are empty api_key = API_KEY
api_key = request.form.get("api_key") or DEFAULT_API_KEY base_url = BASE_URL
base_url = request.form.get("base_url") or DEFAULT_BASE_URL output_folder = OUTPUT_FOLDER
output_folder = request.form.get("output_folder") or DEFAULT_OUTPUT_FOLDER
person_id = request.form["person_id"] person_id = request.form["person_id"]
# Convert numeric parameters with defaults if conversion fails
padding_percent = float(request.form.get("padding_percent", 0.3)) padding_percent = float(request.form.get("padding_percent", 0.3))
resize_width = int(request.form.get("resize_width", 512)) resize_size = int(request.form.get("resize_size", 512))
resize_height = int(request.form.get("resize_height", 512)) face_resolution_threshold = int(request.form.get("face_resolution_threshold", 128))
min_face_width = int(request.form.get("min_face_width", 128))
min_face_height = int(request.form.get("min_face_height", 128))
pose_threshold = float(request.form.get("pose_threshold", 25)) pose_threshold = float(request.form.get("pose_threshold", 25))
desired_left_eye_x = float(request.form.get("desired_left_eye_x", 0.35)) desired_left_eye_x = float(request.form.get("desired_left_eye_x", 0.35))
desired_left_eye_y = float(request.form.get("desired_left_eye_y", 0.45)) desired_left_eye_y = float(request.form.get("desired_left_eye_y", 0.45))
max_workers = int(request.form.get("max_workers", 4)) max_workers = int(request.form.get("max_workers", 4))
face_detect_model_path = request.form.get("face_detect_model_path", "mmod_human_face_detector.dat")
landmark_model_path = request.form.get("landmark_model_path", "shape_predictor_68_face_landmarks.dat")
processed_files = process_faces( processed_files = process_faces(
api_key=api_key, api_key=api_key,
@ -42,15 +38,15 @@ def index():
person_id=person_id, person_id=person_id,
output_folder=output_folder, output_folder=output_folder,
padding_percent=padding_percent, padding_percent=padding_percent,
resize_width=resize_width, resize_width=resize_size,
resize_height=resize_height, resize_height=resize_size,
min_face_width=min_face_width, min_face_width=face_resolution_threshold,
min_face_height=min_face_height, min_face_height=face_resolution_threshold,
pose_threshold=pose_threshold, pose_threshold=pose_threshold,
desired_left_eye=(desired_left_eye_x, desired_left_eye_y), desired_left_eye=(desired_left_eye_x, desired_left_eye_y),
max_workers=max_workers, max_workers=max_workers,
face_detect_model_path=face_detect_model_path, face_detect_model_path=FACE_DETECT_MODEL,
landmark_model_path=landmark_model_path landmark_model_path=LANDMARK_MODEL
) )
result = f"Finished processing. {len(processed_files)} images saved in '{output_folder}'." result = f"Finished processing. {len(processed_files)} images saved in '{output_folder}'."
except Exception as e: except Exception as e:

View file

@ -1,61 +1,128 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8">
<title>Immich Selfie Timelapse Tool</title> <title>Immich Selfie Timelapse Tool</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
margin: 0;
padding: 0;
}
.container {
max-width: 700px;
margin: 50px auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
padding: 30px;
}
h1 {
text-align: center;
color: #333;
}
form {
margin-top: 20px;
}
label {
display: block;
margin-bottom: 10px;
color: #555;
}
input[type="text"],
input[type="number"] {
width: calc(100% - 20px);
padding: 8px 10px;
margin-top: 4px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
details {
background: #eef;
padding: 10px;
margin: 20px 0;
border-radius: 4px;
}
summary {
font-weight: bold;
cursor: pointer;
}
button {
background: #28a745;
color: #fff;
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
display: block;
margin: 20px auto 0;
}
button:hover {
background: #218838;
}
.result, .error {
text-align: center;
margin-top: 20px;
font-size: 16px;
}
.error {
color: #c00;
}
.result {
color: #080;
}
</style>
</head> </head>
<body> <body>
<h1>Create a Selfie Timelapse</h1> <div class="container">
{% if error %} <h1>Immich Selfie Timelapse Tool</h1>
<p style="color:red;">{{ error }}</p> {% if error %}
{% endif %} <p class="error">{{ error }}</p>
<form method="post"> {% endif %}
<label>API Key: <form method="post">
<input type="text" name="api_key" value="{{ request.form.api_key or config['IMMICH_API_KEY'] or '' }}"> <label>
</label><br><br> Person ID:
<label>Base URL: <input type="text" name="person_id" required>
<input type="text" name="base_url" value="{{ request.form.base_url or config['IMMICH_BASE_URL'] or '' }}"> </label>
</label><br><br> <label>
<label>Output Folder: Padding Percent:
<input type="text" name="output_folder" value="{{ request.form.output_folder or config['IMMICH_OUTPUT_FOLDER'] or 'output' }}"> <input type="number" step="0.01" name="padding_percent" value="{{ request.form.padding_percent or 0.3 }}">
</label><br><br> </label>
<label>Person ID: <input type="text" name="person_id" required></label><br><br> <label>
<label>Padding Percent: Output Image Size:
<input type="number" step="0.01" name="padding_percent" value="{{ request.form.padding_percent or 0.3 }}"> <input type="number" name="resize_size" value="{{ request.form.resize_size or 512 }}">
</label><br><br> </label>
<label>Resize Width: <label>
<input type="number" name="resize_width" value="{{ request.form.resize_width or 512 }}"> Face Resolution Threshold:
</label><br><br> <input type="number" name="face_resolution_threshold" value="{{ request.form.face_resolution_threshold or 128 }}">
<label>Resize Height: </label>
<input type="number" name="resize_height" value="{{ request.form.resize_height or 512 }}"> <label>
</label><br><br> Pose Threshold:
<label>Min Face Width: <input type="number" step="0.1" name="pose_threshold" value="{{ request.form.pose_threshold or 25 }}">
<input type="number" name="min_face_width" value="{{ request.form.min_face_width or 128 }}"> </label>
</label><br><br> <details>
<label>Min Face Height: <summary>Advanced Options</summary>
<input type="number" name="min_face_height" value="{{ request.form.min_face_height or 128 }}"> <label>
</label><br><br> Desired Left Eye X:
<label>Pose Threshold: <input type="number" step="0.01" name="desired_left_eye_x" value="{{ request.form.desired_left_eye_x or 0.35 }}">
<input type="number" step="0.1" name="pose_threshold" value="{{ request.form.pose_threshold or 25 }}"> </label>
</label><br><br> <label>
<label>Desired Left Eye X: Desired Left Eye Y:
<input type="number" step="0.01" name="desired_left_eye_x" value="{{ request.form.desired_left_eye_x or 0.35 }}"> <input type="number" step="0.01" name="desired_left_eye_y" value="{{ request.form.desired_left_eye_y or 0.45 }}">
</label><br><br> </label>
<label>Desired Left Eye Y: <label>
<input type="number" step="0.01" name="desired_left_eye_y" value="{{ request.form.desired_left_eye_y or 0.45 }}"> Max Workers:
</label><br><br> <input type="number" name="max_workers" value="{{ request.form.max_workers or 4 }}">
<label>Max Workers: </label>
<input type="number" name="max_workers" value="{{ request.form.max_workers or 4 }}"> </details>
</label><br><br> <button type="submit">Generate Timelapse</button>
<label>Face Detect Model Path: </form>
<input type="text" name="face_detect_model_path" value="{{ request.form.face_detect_model_path or 'mmod_human_face_detector.dat' }}"> {% if result %}
</label><br><br> <p class="result">{{ result }}</p>
<label>Landmark Model Path: {% endif %}
<input type="text" name="landmark_model_path" value="{{ request.form.landmark_model_path or 'shape_predictor_68_face_landmarks.dat' }}"> </div>
</label><br><br>
<button type="submit">Generate Timelapse</button>
</form>
{% if result %}
<h2>{{ result }}</h2>
{% endif %}
</body> </body>
</html> </html>

View file

@ -4,9 +4,7 @@ import os
import io import io
import requests import requests
import concurrent.futures import concurrent.futures
import argparse
from datetime import datetime from datetime import datetime
from PIL import Image, ImageOps from PIL import Image, ImageOps
import numpy as np import numpy as np
import cv2 import cv2
@ -243,7 +241,7 @@ def process_faces(
min_face_height=128, min_face_height=128,
pose_threshold=25, pose_threshold=25,
desired_left_eye=(0.35, 0.45), desired_left_eye=(0.35, 0.45),
max_workers=4, max_workers=1,
face_detect_model_path="mmod_human_face_detector.dat", face_detect_model_path="mmod_human_face_detector.dat",
landmark_model_path="shape_predictor_68_face_landmarks.dat" landmark_model_path="shape_predictor_68_face_landmarks.dat"
): ):