mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-11-06 05:54:07 +00:00
WIP
This commit is contained in:
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):
|
||||
|
||||
Reference in new issue
Block a user