mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-12-18 05:55:45 +00:00
WIP
This commit is contained in:
7
.github/test/Dockerfile-alpine
vendored
7
.github/test/Dockerfile-alpine
vendored
@@ -27,7 +27,12 @@ RUN \
|
|||||||
file \
|
file \
|
||||||
nodejs \
|
nodejs \
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
python3 && \
|
python3 \
|
||||||
|
# OpenCV dependencies for image processing
|
||||||
|
glib \
|
||||||
|
libsm \
|
||||||
|
libxext \
|
||||||
|
libxrender && \
|
||||||
echo "**** pip3 install test of changedetection.io ****" && \
|
echo "**** pip3 install test of changedetection.io ****" && \
|
||||||
python3 -m venv /lsiopy && \
|
python3 -m venv /lsiopy && \
|
||||||
pip install -U pip wheel setuptools && \
|
pip install -U pip wheel setuptools && \
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ RUN --mount=type=cache,id=pip,sharing=locked,target=/tmp/pip-cache \
|
|||||||
--cache-dir=/tmp/pip-cache \
|
--cache-dir=/tmp/pip-cache \
|
||||||
-r /requirements.txt
|
-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
|
# Playwright is an alternative to Selenium
|
||||||
# Excluded this package from requirements.txt to prevent arm/v6 and arm/v7 builds from failing
|
# Excluded this package from requirements.txt to prevent arm/v6 and arm/v7 builds from failing
|
||||||
|
|||||||
@@ -241,9 +241,9 @@ def generate_diff_image_opencv(img_bytes_to, diff_mask):
|
|||||||
# Convert back to PIL Image
|
# Convert back to PIL Image
|
||||||
diff_img = Image.fromarray(result_array.astype(np.uint8))
|
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()
|
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()
|
diff_bytes = buf.getvalue()
|
||||||
|
|
||||||
# Explicit memory cleanup - close files and buffers, delete large objects
|
# 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)
|
diff_array: RGBA diff array from pixelmatch (4D numpy array)
|
||||||
|
|
||||||
Returns:
|
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')
|
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()
|
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()
|
diff_bytes = buf.getvalue()
|
||||||
|
|
||||||
# Explicit memory cleanup - close files first, then delete
|
# Explicit memory cleanup - close files first, then delete
|
||||||
|
|||||||
@@ -157,7 +157,16 @@
|
|||||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 1em;">
|
<div style="color: #666; font-size: 0.9em; margin-bottom: 1em;">
|
||||||
{{ from_version|format_timestamp_timeago }}
|
{{ from_version|format_timestamp_timeago }}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Panel 2: To (Current) -->
|
<!-- Panel 2: To (Current) -->
|
||||||
@@ -166,16 +175,32 @@
|
|||||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 1em;">
|
<div style="color: #666; font-size: 0.9em; margin-bottom: 1em;">
|
||||||
{{ to_version|format_timestamp_timeago }}
|
{{ to_version|format_timestamp_timeago }}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Panel 3: Difference (with red highlights) -->
|
<!-- Panel 3: Difference (with red highlights) -->
|
||||||
<div class="screenshot-panel diff">
|
<div class="screenshot-panel diff">
|
||||||
<h3>Difference Visualization</h3>
|
<h3>Difference Visualization</h3>
|
||||||
<div style="color: #d32f2f; font-size: 0.9em; margin-bottom: 1em; font-weight: bold;">
|
<div style="color: #d32f2f; font-size: 0.9em; margin-bottom: 1em; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 1em;">
|
||||||
Red = Changed Pixels
|
<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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -215,4 +240,41 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user