This commit is contained in:
dgtlmoon
2025-12-16 14:13:31 +01:00
parent b3ceef9aca
commit e47c687f68
4 changed files with 87 additions and 12 deletions

View File

@@ -27,7 +27,12 @@ RUN \
file \
nodejs \
poppler-utils \
python3 && \
python3 \
# OpenCV dependencies for image processing
glib \
libsm \
libxext \
libxrender && \
echo "**** pip3 install test of changedetection.io ****" && \
python3 -m venv /lsiopy && \
pip install -U pip wheel setuptools && \

View File

@@ -43,6 +43,11 @@ RUN --mount=type=cache,id=pip,sharing=locked,target=/tmp/pip-cache \
--cache-dir=/tmp/pip-cache \
-r /requirements.txt
# Copy installed packages from system Python to /dependencies for final image
ARG PYTHON_VERSION
RUN mkdir -p /dependencies && \
cp -r /usr/local/lib/python${PYTHON_VERSION}/site-packages/* /dependencies/
# Playwright is an alternative to Selenium
# Excluded this package from requirements.txt to prevent arm/v6 and arm/v7 builds from failing

View File

@@ -241,9 +241,9 @@ def generate_diff_image_opencv(img_bytes_to, diff_mask):
# Convert back to PIL Image
diff_img = Image.fromarray(result_array.astype(np.uint8))
# Save to bytes as PNG (faster rendering than JPEG for diff images)
# Save to bytes as JPEG (smaller and faster than PNG for diff visualization)
buf = io.BytesIO()
diff_img.save(buf, format='PNG', optimize=True)
diff_img.save(buf, format='JPEG', quality=85, optimize=True)
diff_bytes = buf.getvalue()
# Explicit memory cleanup - close files and buffers, delete large objects
@@ -264,14 +264,17 @@ def generate_diff_image_pixelmatch(diff_array):
diff_array: RGBA diff array from pixelmatch (4D numpy array)
Returns:
bytes: PNG image with highlighted differences
bytes: JPEG image with highlighted differences
"""
# Convert diff array to PIL Image
# Convert diff array to PIL Image (RGBA)
diff_img = Image.fromarray(diff_array.astype(np.uint8), mode='RGBA')
# Save to bytes as PNG
# Convert RGBA to RGB for JPEG (JPEG doesn't support transparency)
diff_img = diff_img.convert('RGB')
# Save to bytes as JPEG (smaller and faster than PNG)
buf = io.BytesIO()
diff_img.save(buf, format='PNG', optimize=True)
diff_img.save(buf, format='JPEG', quality=85, optimize=True)
diff_bytes = buf.getvalue()
# Explicit memory cleanup - close files first, then delete

View File

@@ -157,7 +157,16 @@
<div style="color: #666; font-size: 0.9em; margin-bottom: 1em;">
{{ from_version|format_timestamp_timeago }}
</div>
<img src="data:image/png;base64,{{ img_from_b64 }}" alt="Previous screenshot">
<div style="text-align: center; margin-bottom: 0.5em;">
<a href="#" onclick="downloadImage('img-from', '{{ from_version }}'); return false;" style="color: #0078e7; text-decoration: none; display: inline-flex; align-items: center; gap: 0.3em; font-size: 0.85em;" title="Download previous snapshot">
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" style="display: inline-block;">
<path d="M8 12L3 7h3V1h4v6h3z"/>
<path d="M1 14h14v2H1z"/>
</svg>
Download
</a>
</div>
<img id="img-from" src="data:image/png;base64,{{ img_from_b64 }}" alt="Previous screenshot">
</div>
<!-- Panel 2: To (Current) -->
@@ -166,16 +175,32 @@
<div style="color: #666; font-size: 0.9em; margin-bottom: 1em;">
{{ to_version|format_timestamp_timeago }}
</div>
<img src="data:image/png;base64,{{ img_to_b64 }}" alt="Current screenshot">
<div style="text-align: center; margin-bottom: 0.5em;">
<a href="#" onclick="downloadImage('img-to', '{{ to_version }}'); return false;" style="color: #0078e7; text-decoration: none; display: inline-flex; align-items: center; gap: 0.3em; font-size: 0.85em;" title="Download current snapshot">
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" style="display: inline-block;">
<path d="M8 12L3 7h3V1h4v6h3z"/>
<path d="M1 14h14v2H1z"/>
</svg>
Download
</a>
</div>
<img id="img-to" src="data:image/png;base64,{{ img_to_b64 }}" alt="Current screenshot">
</div>
<!-- Panel 3: Difference (with red highlights) -->
<div class="screenshot-panel diff">
<h3>Difference Visualization</h3>
<div style="color: #d32f2f; font-size: 0.9em; margin-bottom: 1em; font-weight: bold;">
Red = Changed Pixels
<div style="color: #d32f2f; font-size: 0.9em; margin-bottom: 1em; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 1em;">
<a href="#" onclick="downloadImage('diff-image', '{{ to_version }}_diff'); return false;" style="color: #0078e7; text-decoration: none; display: flex; align-items: center; gap: 0.3em;" title="Download difference image">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="display: inline-block;">
<path d="M8 12L3 7h3V1h4v6h3z"/>
<path d="M1 14h14v2H1z"/>
</svg>
Download
</a>
<span>Red = Changed Pixels</span>
</div>
<img src="data:image/png;base64,{{ diff_image_b64 }}" alt="Difference visualization with red highlights">
<img id="diff-image" src="data:image/jpeg;base64,{{ diff_image_b64 }}" alt="Difference visualization with red highlights">
</div>
</div>
@@ -215,4 +240,41 @@
{% endif %}
</div>
<script>
function downloadImage(imageId, filename) {
// Get the image element
const img = document.getElementById(imageId);
const base64Data = img.src;
// Convert base64 to blob
const byteString = atob(base64Data.split(',')[1]);
const mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ab], { type: mimeString });
// Determine file extension from MIME type
const extension = mimeString.includes('jpeg') ? '.jpeg' : '.png';
// Create download link
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename + extension;
document.body.appendChild(a);
a.click();
// Cleanup
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
</script>
{% endblock %}