This commit is contained in:
dgtlmoon committed 2025-12-17 17:02:07 +01:00
1 parent fbb019ddf3
commit 2a405337be
2 files changed
+111 -20

No files matched your search

@@ -390,6 +390,82 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect)
finally:
logger.debug(f"Done '{comparison_method}' in {time.time() - now:.2f}s")
# Check if bounding box is set and draw blue border if present
bounding_box = None
try:
from changedetectionio import processors
processor_instance = processors.difference_detection_processor(datastore, watch.get('uuid'))
processor_name = watch.get('processor', 'default')
config_filename = f'{processor_name}.json'
processor_config = processor_instance.get_extra_watch_config(config_filename)
bounding_box = processor_config.get('bounding_box') if processor_config else None
if bounding_box:
logger.debug(f"Drawing blue bounding box on diff images: {bounding_box}")
# Parse bounding box: "x,y,width,height"
parts = [int(p.strip()) for p in bounding_box.split(',')]
if len(parts) == 4:
from PIL import Image, ImageDraw
# Draw on "from" image
img_from_pil = Image.open(io.BytesIO(img_bytes_from))
draw_from = ImageDraw.Draw(img_from_pil)
x, y, width, height = parts
# Draw blue rectangle (3px border)
for offset in range(3):
draw_from.rectangle(
[x + offset, y + offset, x + width - offset, y + height - offset],
outline='blue'
)
buf_from = io.BytesIO()
img_from_pil.save(buf_from, format='PNG')
img_bytes_from = buf_from.getvalue()
img_from_pil.close()
buf_from.close()
# Draw on "to" image
img_to_pil = Image.open(io.BytesIO(img_bytes_to))
original_width = img_to_pil.width
original_height = img_to_pil.height
draw_to = ImageDraw.Draw(img_to_pil)
for offset in range(3):
draw_to.rectangle(
[x + offset, y + offset, x + width - offset, y + height - offset],
outline='blue'
)
buf_to = io.BytesIO()
img_to_pil.save(buf_to, format='PNG')
img_bytes_to = buf_to.getvalue()
img_to_pil.close()
buf_to.close()
# Draw on diff image
img_diff_pil = Image.open(io.BytesIO(diff_image_bytes))
# Need to scale the bounding box if image was resized for diff
scale_x = img_diff_pil.width / original_width if original_width > 0 else 1
scale_y = img_diff_pil.height / original_height if original_height > 0 else 1
draw_diff = ImageDraw.Draw(img_diff_pil)
x_scaled = int(x * scale_x)
y_scaled = int(y * scale_y)
width_scaled = int(width * scale_x)
height_scaled = int(height * scale_y)
logger.debug(f"Diff image size: {img_diff_pil.size}, original: {original_width}x{original_height}, scale: {scale_x:.2f}x{scale_y:.2f}")
logger.debug(f"Drawing blue box on diff: ({x_scaled},{y_scaled}) {width_scaled}x{height_scaled}")
for offset in range(3):
draw_diff.rectangle(
[x_scaled + offset, y_scaled + offset,
x_scaled + width_scaled - offset, y_scaled + height_scaled - offset],
outline='blue'
)
buf_diff = io.BytesIO()
img_diff_pil.save(buf_diff, format='JPEG', quality=85)
diff_image_bytes = buf_diff.getvalue()
img_diff_pil.close()
buf_diff.close()
logger.debug(f"Successfully drew blue bounding box on all three images")
except Exception as e:
logger.warning(f"Failed to draw bounding box on diff images: {e}")
# Convert images to base64 for embedding in template
img_from_b64 = base64.b64encode(img_bytes_from).decode('utf-8')
img_to_b64 = base64.b64encode(img_bytes_to).decode('utf-8')
@@ -152,17 +152,12 @@ class perform_site_check(difference_detection_processor):
except Exception as e:
logger.warning(f"Failed to parse xpath_data for visual selector: {e}")
# Crop the current image if region was found
# Crop the current image if region was found (for comparison only, keep full screenshot for history)
cropped_current_img = None
if crop_region:
try:
current_img = current_img.crop(crop_region)
# Update self.screenshot to the cropped version for history storage
crop_buffer = io.BytesIO()
current_img.save(crop_buffer, format='PNG')
self.screenshot = self.fetcher.screenshot = crop_buffer.getvalue()
logger.debug(f"Cropped screenshot to {current_img.size} (region: {crop_region})")
cropped_current_img = current_img.crop(crop_region)
logger.debug(f"Cropped screenshot to {cropped_current_img.size} (region: {crop_region}) for comparison")
except Exception as e:
logger.error(f"Failed to crop screenshot: {e}")
crop_region = None # Disable cropping on error
@@ -173,9 +168,12 @@ class perform_site_check(difference_detection_processor):
# First check - save baseline, no comparison
logger.info(f"First check for watch {watch.get('uuid')} - saving baseline screenshot")
# Close the PIL image before returning
# Close the PIL images before returning
current_img.close()
del current_img
if cropped_current_img:
cropped_current_img.close()
del cropped_current_img
update_obj = {
'previous_md5': hashlib.md5(self.screenshot).hexdigest(),
@@ -196,20 +194,24 @@ class perform_site_check(difference_detection_processor):
previous_img = Image.open(io.BytesIO(previous_screenshot_bytes))
# Crop previous image to the same region if visual selector is enabled
# Crop previous image to the same region if cropping is enabled
cropped_previous_img = None
if crop_region:
try:
previous_img = previous_img.crop(crop_region)
logger.debug(f"Cropped previous screenshot to {previous_img.size}")
cropped_previous_img = previous_img.crop(crop_region)
logger.debug(f"Cropped previous screenshot to {cropped_previous_img.size}")
except Exception as e:
logger.warning(f"Failed to crop previous screenshot: {e}")
except Exception as e:
logger.warning(f"Failed to load previous screenshot for comparison: {e}")
# Clean up current image before returning
# Clean up current images before returning
if 'current_img' in locals():
current_img.close()
del current_img
if 'cropped_current_img' in locals() and cropped_current_img:
cropped_current_img.close()
del cropped_current_img
# If we can't load previous, treat as first check
update_obj = {
@@ -222,19 +224,26 @@ class perform_site_check(difference_detection_processor):
# Perform comparison based on selected method
try:
# Use cropped versions if available, otherwise use full images
img_for_comparison_prev = cropped_previous_img if cropped_previous_img else previous_img
img_for_comparison_curr = cropped_current_img if cropped_current_img else current_img
# Ensure images are the same size
if current_img.size != previous_img.size:
logger.info(f"Resizing images to match: {previous_img.size} -> {current_img.size}")
previous_img = previous_img.resize(current_img.size, Image.Resampling.LANCZOS)
if img_for_comparison_curr.size != img_for_comparison_prev.size:
logger.info(f"Resizing images to match: {img_for_comparison_prev.size} -> {img_for_comparison_curr.size}")
img_for_comparison_prev = img_for_comparison_prev.resize(img_for_comparison_curr.size, Image.Resampling.LANCZOS)
# If we resized a cropped version, update the reference
if cropped_previous_img:
cropped_previous_img = img_for_comparison_prev
if comparison_method == 'pixelmatch':
changed_detected, change_score = self._compare_pixelmatch(
previous_img, current_img, threshold
img_for_comparison_prev, img_for_comparison_curr, threshold
)
logger.info(f"Pixelmatch: {change_score:.2f}% pixels different, threshold: {threshold*100:.0f}%")
else: # opencv (default)
changed_detected, change_score = self._compare_opencv(
previous_img, current_img, threshold
img_for_comparison_prev, img_for_comparison_curr, threshold
)
logger.info(f"OpenCV: {change_score:.2f}% pixels changed, threshold: {threshold:.0f}")
@@ -243,12 +252,18 @@ class perform_site_check(difference_detection_processor):
previous_img.close()
del current_img
del previous_img
if cropped_current_img:
cropped_current_img.close()
del cropped_current_img
if cropped_previous_img:
cropped_previous_img.close()
del cropped_previous_img
del previous_screenshot_bytes # Release the large bytes object
except Exception as e:
logger.error(f"Failed to compare screenshots: {e}")
# Ensure cleanup even on error
for obj in ['current_img', 'previous_img']:
for obj in ['current_img', 'previous_img', 'cropped_current_img', 'cropped_previous_img']:
try:
locals()[obj].close()
except (KeyError, NameError, AttributeError):