Add warning if files present in output folder, fix cancellation button
This commit is contained in:
parent
23939cccdd
commit
e7075dbec7
3 changed files with 87 additions and 21 deletions
58
main.py
58
main.py
|
|
@ -8,9 +8,9 @@ from timelapse import process_faces, ProcessConfig, validate_immich_connection
|
|||
app = Flask(__name__)
|
||||
|
||||
# Critical parameters provided via environment variables
|
||||
API_KEY = os.environ.get("IMMICH_API_KEY", "")
|
||||
BASE_URL = os.environ.get("IMMICH_BASE_URL", "")
|
||||
OUTPUT_FOLDER = os.environ.get("OUTPUT_FOLDER", "output")
|
||||
API_KEY = "4ryTBWjXRsaTUHMfOurAC1fcBktEgtoZvMR3lwZUluI"
|
||||
BASE_URL = "http://192.168.1.94:2283/api"
|
||||
OUTPUT_FOLDER = "output"
|
||||
|
||||
# Model paths
|
||||
FACE_DETECT_MODEL = "mmod_human_face_detector.dat"
|
||||
|
|
@ -23,6 +23,8 @@ AVAILABLE_CORES = multiprocessing.cpu_count()
|
|||
progress_info = {"completed": 0, "total": 0, "status": "idle"}
|
||||
# Global processing thread reference
|
||||
processing_thread = None
|
||||
# Global flag to signal cancellation
|
||||
cancel_requested = False
|
||||
|
||||
|
||||
def update_progress(current, total):
|
||||
|
|
@ -55,6 +57,7 @@ def background_process(person_id, padding_percent, resize_size, face_resolution_
|
|||
compile_video (bool): Whether to compile the images into a video.
|
||||
framerate (int): Frames per second for the output video.
|
||||
"""
|
||||
global cancel_requested
|
||||
try:
|
||||
progress_info["status"] = "running"
|
||||
# Build the configuration object for processing
|
||||
|
|
@ -73,8 +76,16 @@ def background_process(person_id, padding_percent, resize_size, face_resolution_
|
|||
face_detect_model_path=FACE_DETECT_MODEL,
|
||||
landmark_model_path=LANDMARK_MODEL
|
||||
)
|
||||
|
||||
# Pass the cancel flag to the process_faces function
|
||||
processed_files = process_faces(config, max_workers=max_workers, progress_callback=update_progress,
|
||||
date_from=date_from, date_to=date_to)
|
||||
date_from=date_from, date_to=date_to, cancel_flag=lambda: cancel_requested)
|
||||
|
||||
# Check if processing was cancelled
|
||||
if cancel_requested:
|
||||
progress_info["status"] = "cancelled"
|
||||
cancel_requested = False
|
||||
return
|
||||
|
||||
# Compile video if requested and there are processed files
|
||||
if compile_video and processed_files:
|
||||
|
|
@ -100,6 +111,21 @@ def background_process(person_id, padding_percent, resize_size, face_resolution_
|
|||
progress_info["status"] = f"error: {e}"
|
||||
|
||||
|
||||
def check_output_folder():
|
||||
"""
|
||||
Checks if the output folder is empty.
|
||||
|
||||
Returns:
|
||||
tuple: (is_empty, file_count) - Boolean indicating if folder is empty and number of files
|
||||
"""
|
||||
if not os.path.exists(OUTPUT_FOLDER):
|
||||
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
||||
return True, 0
|
||||
|
||||
files = [f for f in os.listdir(OUTPUT_FOLDER) if os.path.isfile(os.path.join(OUTPUT_FOLDER, f))]
|
||||
return len(files) == 0, len(files)
|
||||
|
||||
|
||||
@app.route("/progress")
|
||||
def progress():
|
||||
"""
|
||||
|
|
@ -122,12 +148,16 @@ def cancel():
|
|||
"""
|
||||
Endpoint to cancel the current processing job.
|
||||
"""
|
||||
global processing_thread
|
||||
global processing_thread, cancel_requested
|
||||
|
||||
# Set the cancel flag
|
||||
cancel_requested = True
|
||||
|
||||
if processing_thread and processing_thread.is_alive():
|
||||
# We can't truly kill a thread in Python, but we can signal it to stop
|
||||
progress_info["status"] = "cancelled"
|
||||
return jsonify({"success": True, "message": "Processing cancelled."})
|
||||
else:
|
||||
cancel_requested = False
|
||||
return jsonify({"success": False, "message": "No active processing to cancel."})
|
||||
|
||||
|
||||
|
|
@ -136,19 +166,29 @@ def index():
|
|||
"""
|
||||
Index route that displays the form and starts processing in a background thread on POST.
|
||||
"""
|
||||
global processing_thread
|
||||
global processing_thread, cancel_requested
|
||||
|
||||
result = None
|
||||
error = None
|
||||
warning = None
|
||||
|
||||
# Check if output folder is empty
|
||||
is_empty, file_count = check_output_folder()
|
||||
if not is_empty:
|
||||
warning = f"Output folder is not empty. Contains {file_count} files. New images will be added to this folder."
|
||||
|
||||
# Check if Immich server connection is valid
|
||||
is_valid, message = validate_immich_connection(API_KEY, BASE_URL)
|
||||
if not is_valid and request.method == "POST":
|
||||
error = f"Immich server connection error: {message}"
|
||||
return render_template("index.html", error=error, max_workers_options=list(range(1, AVAILABLE_CORES + 1)))
|
||||
return render_template("index.html", error=error, warning=warning,
|
||||
max_workers_options=list(range(1, AVAILABLE_CORES + 1)))
|
||||
|
||||
if request.method == "POST":
|
||||
try:
|
||||
# Make sure any previous cancel request is cleared
|
||||
cancel_requested = False
|
||||
|
||||
person_id = request.form["person_id"]
|
||||
padding_percent = float(request.form.get("padding_percent", 30)) / 100
|
||||
resize_size = int(request.form.get("resize_size", 512))
|
||||
|
|
@ -180,7 +220,7 @@ def index():
|
|||
except Exception as e:
|
||||
error = f"Error processing request: {e}"
|
||||
|
||||
return render_template("index.html", result=result, error=error,
|
||||
return render_template("index.html", result=result, error=error, warning=warning,
|
||||
max_workers_options=list(range(1, AVAILABLE_CORES + 1)))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@
|
|||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
.warning {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
margin: 15px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
@ -145,6 +153,10 @@
|
|||
Checking Immich server connection...
|
||||
</div>
|
||||
|
||||
{% if warning %}
|
||||
<div class="warning">{{ warning }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if error %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endif %}
|
||||
|
|
|
|||
38
timelapse.py
38
timelapse.py
|
|
@ -392,7 +392,10 @@ def process_asset_wrapper(args):
|
|||
return process_asset_worker(asset, config)
|
||||
|
||||
|
||||
def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None, date_from=None, date_to=None):
|
||||
# Replacing only the process_faces function in timelapse.py
|
||||
|
||||
def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None, date_from=None, date_to=None,
|
||||
cancel_flag=None):
|
||||
"""
|
||||
Processes assets containing the person and saves aligned face images.
|
||||
|
||||
|
|
@ -405,6 +408,7 @@ def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None,
|
|||
progress_callback (callable, optional): A callback function for progress updates.
|
||||
date_from (str, optional): Start date for filtering assets.
|
||||
date_to (str, optional): End date for filtering assets.
|
||||
cancel_flag (callable, optional): A function that returns True if processing should be cancelled.
|
||||
|
||||
Returns:
|
||||
list: A list of file paths of the saved images.
|
||||
|
|
@ -412,8 +416,7 @@ def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None,
|
|||
os.makedirs(config.output_folder, exist_ok=True)
|
||||
|
||||
# Check if we need to stop processing
|
||||
if progress_callback and hasattr(progress_callback, "__self__") and getattr(progress_callback.__self__, "status",
|
||||
"") == "cancelled":
|
||||
if cancel_flag and cancel_flag():
|
||||
logger.info("Processing was cancelled.")
|
||||
return []
|
||||
|
||||
|
|
@ -432,17 +435,28 @@ def process_faces(config: ProcessConfig, max_workers=1, progress_callback=None,
|
|||
initargs=initializer_args) as executor:
|
||||
# Pack arguments for each asset
|
||||
tasks = ((asset, config) for asset in assets)
|
||||
for result in tqdm(executor.map(process_asset_wrapper, tasks), total=total_assets):
|
||||
# Check if we need to stop processing
|
||||
if progress_callback and hasattr(progress_callback, "__self__") and getattr(progress_callback.__self__,
|
||||
"status", "") == "cancelled":
|
||||
logger.info("Processing was cancelled.")
|
||||
executor.shutdown(wait=False)
|
||||
break
|
||||
future_to_asset = {executor.submit(process_asset_wrapper, args): args[0] for args in
|
||||
((asset, config) for asset in assets)}
|
||||
|
||||
for future in tqdm(concurrent.futures.as_completed(future_to_asset), total=total_assets):
|
||||
# Check if we need to stop processing
|
||||
if cancel_flag and cancel_flag():
|
||||
logger.info("Processing was cancelled.")
|
||||
# Cancel all pending futures
|
||||
for f in future_to_asset:
|
||||
f.cancel()
|
||||
executor.shutdown(wait=False)
|
||||
return processed_files
|
||||
|
||||
try:
|
||||
result = future.result()
|
||||
if result is not None:
|
||||
processed_files.append(result)
|
||||
except Exception as e:
|
||||
logger.info(f"Asset processing failed: {e}")
|
||||
|
||||
if result is not None:
|
||||
processed_files.append(result)
|
||||
if progress_callback:
|
||||
progress_callback(len(processed_files), total_assets)
|
||||
|
||||
logger.info(f"Finished processing. {len(processed_files)} images saved.")
|
||||
return processed_files
|
||||
|
|
|
|||
Loading…
Reference in a new issue