pull/938/head
dgtlmoon 2 years ago
parent 20869a13b3
commit 41db6652fe

@ -782,6 +782,48 @@ def changedetection_app(config=None, datastore_o=None):
return redirect(url_for('index'))
@app.route("/diff/image/<string:uuid>", methods=['GET'])
@login_required
def diff_image_history_page(uuid):
# More for testing, possible to return the first/only
if uuid == 'first':
uuid = list(datastore.data['watching'].keys()).pop()
extra_stylesheets = [url_for('static_content', group='styles', filename='diff.css')]
try:
watch = datastore.data['watching'][uuid]
except KeyError:
flash("No history found for the specified link, bad link?", "error")
return redirect(url_for('index'))
history = watch.history
dates = list(history.keys())
if len(dates) < 2:
flash("Not enough saved change detection snapshots to produce a report.", "error")
return redirect(url_for('index'))
previous_version = dates[-2]
output = render_template("diff-image.html",
watch=watch,
extra_stylesheets=extra_stylesheets,
versions=dates[:-1], # All except current/last
uuid=uuid,
newest_version_timestamp=dates[-1],
current_previous_version=str(previous_version),
current_diff_url=watch['url'],
extra_title=" - Diff - {}".format(watch['title'] if watch['title'] else watch['url']),
left_sticky=True,
last_error=watch['last_error'],
last_error_text=watch.get_error_text(),
last_error_screenshot=watch.get_error_snapshot()
)
return output
@app.route("/diff/<string:uuid>", methods=['GET'])
@login_required
def diff_history_page(uuid):
@ -947,6 +989,61 @@ def changedetection_app(config=None, datastore_o=None):
return output
@app.route("/preview/image/<string:uuid>/<string:history_timestamp>")
def render_single_image(uuid, history_timestamp):
watch = datastore.data['watching'].get(uuid)
dates = list(watch.history.keys())
if not history_timestamp or history_timestamp == 'None':
history_timestamp = dates[-2]
filename = watch.history[history_timestamp]
with open(filename, 'rb') as f:
img = f.read()
response = make_response(img)
response.headers['Content-type'] = 'image/png'
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = 0
return response
# Diff renderer for images
# Renders the diff which includes the red box around what changes
# We always compare the newest against whatever compare_date we are given
@app.route("/diff/image/<string:uuid>/<string:compare_date>")
def render_diff_image(uuid, compare_date):
from changedetectionio import image_diff
from flask import make_response
watch = datastore.data['watching'].get(uuid)
dates = list(watch.history.keys())
if len(dates) < 2:
flash("Not enough saved change detection snapshots to produce a report.", "error")
return redirect(url_for('index'))
if not compare_date or compare_date == 'None':
compare_date = dates[-2]
new_img = watch.history[watch.newest_history_key]
prev_img = watch.history[compare_date]
img = image_diff.render_diff(new_img, prev_img)
resp = make_response(img)
resp.headers['Content-Type'] = 'image/jpeg'
return resp
@app.route("/settings/notification-logs", methods=['GET'])
@login_required
def notification_logs():
@ -1095,12 +1192,16 @@ def changedetection_app(config=None, datastore_o=None):
return redirect(url_for('index'))
url = request.form.get('url').strip()
fetch_processor =request.form.get('fetch_processor').strip()
if datastore.url_exists(url):
flash('The URL {} already exists'.format(url), "error")
return redirect(url_for('index'))
add_paused = request.form.get('edit_and_watch_submit_button') != None
new_uuid = datastore.add_watch(url=url, tag=request.form.get('tag').strip(), extras={'paused': add_paused})
new_uuid = datastore.add_watch(url=url,
tag=request.form.get('tag').strip(),
extras={'paused': add_paused, 'fetch_processor': fetch_processor}
)
if not add_paused and new_uuid:
@ -1241,7 +1342,7 @@ def changedetection_app(config=None, datastore_o=None):
return redirect(url_for('index'))
@app.route("/api/share-url", methods=['GET'])
@app.route("/api/r-url", methods=['GET'])
@login_required
def form_share_put_watch():
"""Given a watch UUID, upload the info and return a share-link

@ -0,0 +1,40 @@
from skimage.metrics import structural_similarity as compare_ssim
import argparse
import imutils
import cv2
# From https://www.pyimagesearch.com/2017/06/19/image-difference-with-opencv-and-python/
def render_diff(fpath_imageA, fpath_imageB):
imageA = cv2.imread(fpath_imageA)
imageB = cv2.imread(fpath_imageB)
# convert the images to grayscale
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)
# compute the Structural Similarity Index (SSIM) between the two
# images, ensuring that the difference image is returned
(score, diff) = compare_ssim(grayA, grayB, full=True)
diff = (diff * 255).astype("uint8")
print("SSIM: {}".format(score))
# threshold the difference image, followed by finding contours to
# obtain the regions of the two input images that differ
thresh = cv2.threshold(diff, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# loop over the contours
for c in cnts:
# compute the bounding box of the contour and then draw the
# bounding box on both input images to represent where the two
# images differ
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(imageA, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.rectangle(imageB, (x, y), (x + w, y + h), (0, 0, 255), 2)
#return cv2.imencode('.jpg', imageB)[1].tobytes()
return cv2.imencode('.jpg', imageA)[1].tobytes()

@ -27,7 +27,7 @@ class model(dict):
'extract_text': [], # Extract text by regex after filters
'extract_title_as_title': False,
'fetch_backend': None,
'fetch_processor': None, # default None, json_html_plaintext, image
'fetch_processor': 'json_html_plaintext', # json_html_plaintext, image
'filter_failure_notification_send': strtobool(os.getenv('FILTER_FAILURE_NOTIFICATION_SEND_DEFAULT', 'True')),
'headers': {}, # Extra headers to send
'ignore_text': [], # List of text to ignore when calculating the comparison checksum

@ -0,0 +1,51 @@
{% extends 'base.html' %}
{% block content %}
<div id="settings">
<h1>Differences</h1>
<form class="pure-form " action="" method="GET">
<fieldset>
{% if versions|length >= 1 %}
<label for="diff-version">Compare newest (<span id="current-v-date"></span>) with</label>
<select id="diff-version" name="previous_version">
{% for version in versions %}
<option value="{{version}}" {% if version== current_previous_version %} selected="" {% endif %}>
{{version}}
</option>
{% endfor %}
</select>
<button type="submit" class="pure-button pure-button-primary">Go</button>
{% endif %}
</fieldset>
</form>
</div>
<div id="diff-ui">
<img style="max-width: 100%" src="{{ url_for('render_diff_image', uuid=uuid, compare_date=current_previous_version) }}" />
<img style="max-width: 100%" src="{{ url_for('render_single_image', uuid=uuid, history_timestamp=current_previous_version) }}" />
</div>
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='diff.js')}}"></script>
<script defer="">
window.onload = function() {
/* Set current version date as local time in the browser also */
var current_v = document.getElementById("current-v-date");
var dateObject = new Date({{ newest_version_timestamp }}*1000);
current_v.innerHTML=dateObject.toLocaleString();
/* Convert what is options from UTC time.time() to local browser time */
var diffList=document.getElementById("diff-version");
if (typeof(diffList) != 'undefined' && diffList != null) {
for (var option of diffList.options) {
var dateObject = new Date(option.value*1000);
option.label=dateObject.toLocaleString();
}
}
}
</script>
{% endblock %}

@ -31,7 +31,6 @@
{% if 'text-filters-and-triggers' in enabled_tabs %}
<li class="tab"><a href="#filters-and-triggers">Filters &amp; Triggers</a></li>
{%endif%}
<li class="tab"><a href="#notifications">Notifications</a></li>
</ul>
</div>

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<div id="settings">
<h1>Preview</h1>
</div>
<div id="diff-ui">
<img style="max-width: 100%" src="{{ url_for('render_single_image', uuid=uuid, date=current_previous_version) }}" />
</div>
{% endblock %}

@ -116,7 +116,12 @@
class="recheck pure-button button-small pure-button-primary">{% if watch.uuid in queued_uuids %}Queued{% else %}Recheck{% endif %}</a>
<a href="{{ url_for('edit_page', uuid=watch.uuid)}}" class="pure-button button-small pure-button-primary">Edit</a>
{% if watch.history_n >= 2 %}
<a href="{{ url_for('diff_history_page', uuid=watch.uuid) }}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary diff-link">Diff</a>
{% if watch.fetch_processor == "image" %}
<a href="{{ url_for('diff_image_history_page', uuid=watch.uuid) }}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary diff-link">Diff</a>
{% else %}
<a href="{{ url_for('diff_history_page', uuid=watch.uuid) }}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary diff-link">Diff</a>
{% endif %}
{% else %}
{% if watch.history_n == 1 or (watch.history_n ==0 and watch.error_text_ctime )%}
<a href="{{ url_for('preview_page', uuid=watch.uuid)}}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Preview</a>

@ -46,4 +46,7 @@ werkzeug ~= 2.0.0
imagehash ~= 4.3.0
pillow
scikit-image
imutils
opencv-python
python-magic

Loading…
Cancel
Save