Module lib.webpage.historicpage
Function define the web page to view recent motion detection
Expand source code
# Distributed under Pycameresp License
# Copyright (c) 2023 Remi BERTHOLET
""" Function define the web page to view recent motion detection """
# pylint:disable=anomalous-unicode-escape-in-string
import server.httpserver
from htmltemplate import *
import webpage.mainpage
import webpage.streamingpage
import motion
import video.video
import tools.lang
import tools.info
import tools.strings
import tools.tasking
def get_days_pagination(last_days, request):
""" Get the pagination html part of days """
current_day = b""
last_days = list(last_days)
if len(last_days) > 0:
last_days.sort()
last_days.reverse()
pages = []
day_id = int(request.params.get(b"day_id",0))
max_days = 5
if len(last_days) > max_days:
if day_id - 2 < 0:
begin_day = 0
end_day = len(last_days) if len(last_days) < max_days else max_days
elif day_id + 3 > len(last_days):
end_day = len(last_days)
begin_day = 0 if len(last_days) < max_days else len(last_days) - max_days
else:
begin_day = day_id - max_days // 2
end_day = day_id + (max_days//2 + 1)
if begin_day > 0:
pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(begin_day-1)))
else:
begin_day = 0
end_day = len(last_days)
i = begin_day
for day in last_days[begin_day:end_day]:
pages.append(PageItem(text=day[8:10],
class_=b"",
active=b"active" if day_id == i else b"",
href=b"/historic?day_id=%d"%i if day_id != i else b""))
if day_id == i:
current_day = day
i += 1
if len(last_days) > max_days:
if end_day < len(last_days):
pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(end_day)))
pagination_begin = Pagination(pages, class_=b"pagination-sm", id=b"pagination_begin")
pagination_end = Pagination(pages, class_=b"pagination-sm", id=b"pagination_end")
result = pagination_begin, pagination_end,current_day
else:
result = None,None,current_day
return result
@server.httpserver.HttpServer.add_route(b'/historic', menu=tools.lang.menu_motion, item=tools.lang.item_historic, available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion)
async def historic(request, response, args):
""" motion.historic.Historic motion detection page """
webpage.streamingpage.Streaming.stop()
motion.historic.Historic.get_root()
last_days = await motion.historic.Historic.get_last_days()
pagination_begin, pagination_end,current_day = get_days_pagination(last_days, request)
if pagination_end is not None and pagination_begin is not None:
page_content = [\
pagination_begin,
Tag(b"""
<div class="modal" id="zoom_window">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" >
<canvas id="zoom_image" class="w-100" data-bs-dismiss="modal"/>
</div>
</div>
</div>
</div>
<script type='text/javascript'>
window.onload = load_historic;
var current_day = '%s';
var historic = null;
var last_id = 0;
var historic_request = new XMLHttpRequest();
var image_request = new XMLHttpRequest();
const MOTION_FILENAME =0;
const MOTION_WIDTH =1;
const MOTION_HEIGHT =2;
const MOTION_DIFFS =3;
const MOTION_SQUAREX =4;
const MOTION_SQUAREY =5;
function load_historic()
{
historic_request.onreadystatechange = historic_loaded;
historic_request.open("GET","historic/historic.json",true);
historic_request.send();
}
function historic_loaded()
{
if (historic_request.readyState === XMLHttpRequest.DONE)
{
if (historic_request.status === 200)
{
historic = JSON.parse(historic_request.responseText);
select_day();
load_image();
}
}
}
function load_image()
{
if (historic.length > 0)
{
var motion = historic[last_id];
image_request.onreadystatechange = image_loaded;
image_request.open("GET","/historic/images/" + motion[MOTION_FILENAME],true);
image_request.send();
}
}
function rtrim(x, characters)
{
var start = 0;
var end = x.length - 1;
while (characters.indexOf(x[end]) >= 0)
{
end -= 1;
}
return x.substr(0, end + 1);
}
function ltrim(x, characters)
{
var start = 0;
while (characters.indexOf(x[start]) >= 0)
{
start += 1;
}
var end = x.length - 1;
return x.substr(start);
}
function get_day(id)
{
var filename = historic[id][MOTION_FILENAME];
filename = ltrim(filename, "/");
filename = filename.split("/");
return filename[1]+"/"+filename[2]+"/"+filename[3];
}
function select_day()
{
if (historic.length > 0)
{
for (i = 0; i < historic.length; i++)
{
if (get_day(i) == current_day)
{
last_id = i;
break;
}
}
}
}
function get_quality()
{
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent))
{
return 1;
}
else
{
return 2;
}
}
function image_loaded()
{
if (image_request.readyState === XMLHttpRequest.DONE)
{
if (image_request.status === 200)
{
var motion = historic[last_id];
var div = document.createElement("div");
div.className = "col-lg-2 pb-1";
var canvas = document.createElement("canvas");
canvas.width = motion[MOTION_WIDTH ] * get_quality();
canvas.height = motion[MOTION_HEIGHT] * get_quality();
canvas.id = last_id;
canvas.className = "w-100";
canvas.setAttribute("data-bs-toggle","modal");
canvas.setAttribute("data-bs-target","#zoom_window");
canvas.onclick = e =>
{
var view = document.getElementById('zoom_image');
var destCtx = view.getContext('2d');
view.width = motion[MOTION_WIDTH ] * get_quality();
view.height = motion[MOTION_HEIGHT] * get_quality();
destCtx.drawImage(canvas, 0, 0);
};
var image = new Image();
image.src = 'data:image/jpeg;base64,' + image_request.response;
image.onload = function(){show_motion(canvas.id, image);};
div.appendChild(canvas);
if (last_id == 0)
{
document.getElementById('motions').replaceChildren(div);
}
else
{
document.getElementById('motions').appendChild(div);
}
last_id = last_id + 1;
if (last_id < historic.length-1)
{
if (get_day(last_id) == current_day)
{
setTimeout(load_image, 1);
}
}
}
else
{
setTimeout(load_image, 1);
}
}
}
function get_difference(motion, x, y)
{
var squarex = motion[MOTION_SQUAREX];
var maxx = motion[MOTION_WIDTH] /squarex;
var bitpos = y*maxx + x;
if (typeof motion[MOTION_DIFFS] === 'string')
{
return motion[MOTION_DIFFS][bitpos];
}
else
{
var word = parseInt(bitpos/32);
var bit = 31-bitpos%%32;
var mask = 1 << bit;
var val = motion[MOTION_DIFFS][word];
if (val & mask)
{
return "#";
}
else
{
return " ";
}
}
}
function show_motion(id, image)
{
var x;
var y;
var motion = historic[id];
var canvas = document.getElementById(id);
var ctx = canvas.getContext('2d');
var squarex = motion[MOTION_SQUAREX] * get_quality();
var squarey = motion[MOTION_SQUAREY] * get_quality();
var maxx = (motion[MOTION_WIDTH] /squarex) * get_quality();
var maxy = (motion[MOTION_HEIGHT]/squarey) * get_quality();
ctx.drawImage(image, 0, 0, motion[MOTION_WIDTH ] , motion[MOTION_HEIGHT], 0, 0, motion[MOTION_WIDTH ] * get_quality(), motion[MOTION_HEIGHT] * get_quality());
ctx.strokeStyle = "red";
ctx.lineWidth = 1 * get_quality();
for (y = 0; y < maxy; y ++)
{
for (x = 0; x < maxx; x ++)
{
var detection = get_difference(motion, x, y);
if (x >= 1)
{
var previous = get_difference(motion, x-1, y);
if (previous != detection)
{
ctx.beginPath();
ctx.moveTo(0 + x*squarex, 0 + y*squarey);
ctx.lineTo(0 + x*squarex, 0 + y*squarey + squarey);
ctx.stroke();
}
}
}
}
for (x = 0; x < maxx; x ++)
{
for (y = 0; y < maxy; y ++)
{
var detection = get_difference(motion, x, y);
if (y >= 1)
{
var previous = get_difference(motion, x, y-1);
if (previous != detection)
{
ctx.beginPath();
ctx.moveTo(0 + x*squarex, 0 + y*squarey);
ctx.lineTo(0 + x*squarex + squarex, 0 + y*squarey);
ctx.stroke();
}
}
}
}
var font_size = 35 * get_quality();
ctx.font = font_size + "px monospace";
var width = ctx.measureText(get_name(motion[MOTION_FILENAME])).width;
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fillRect(0,(motion[MOTION_HEIGHT] * get_quality())-font_size,
width+get_quality(), font_size+10);
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.fillText(get_name(motion[MOTION_FILENAME]), 3, (motion[MOTION_HEIGHT] * get_quality() - 5*get_quality() ));
}
// Convert the filename into text displayed
function get_name(filename)
{
filename = filename.split(".")[0];
lst = filename.split("/");
filename = lst[lst.length-1];
filename = filename.replace("D= ","D=");
spl = filename.split(" ");
if (spl.length == 3)
{
date = spl[0].split("_")[0];
hour = spl[0].split("_")[1];
date = date.replaceAll("-","/") + " " + hour.replaceAll("-",":");
last = spl[1] + " " + spl[2];
result = date + " "+ last;
}
else
{
result = filename;
}
return result;
}
function get_date(filename)
{
return get_name(filename).substring(0,10);
}
</script>
<div id="motions" class="row"></div>
"""%(current_day)),
Br(),
pagination_end
]
else:
page_content = Tag(b"<span>%s</span>"%tools.lang.historic_not_available)
page = webpage.mainpage.main_frame(request, response, args,tools.lang.last_motion_detections,Form(page_content))
await response.send_page(page)
@server.httpserver.HttpServer.add_route(b'/historic/historic.json', available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion)
async def historic_json(request, response, args):
""" Send historic json file """
tools.tasking.Tasks.slow_down()
try:
await response.send_buffer(b"historic.json", await motion.historic.Historic.get_json())
except Exception as err:
await response.send_not_found(err)
@server.httpserver.HttpServer.add_route(b'/historic/images/.*', available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion)
async def historic_image(request, response, args):
""" Send historic image """
tools.tasking.Tasks.slow_down()
reserved = await video.video.Camera.reserve(motion.historic.Historic, timeout=5, suspension=10)
try:
if reserved:
await motion.historic.Historic.acquire()
await response.send_file(tools.strings.tostrings(request.path[len("/historic/images/"):]), base64=True)
else:
await response.send_not_found()
finally:
if reserved:
await motion.historic.Historic.release()
await video.video.Camera.unreserve(motion.historic.Historic)
@server.httpserver.HttpServer.add_route(b'/historic/download/.*', available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion)
async def download_image(request, response, args):
""" Download historic image """
tools.tasking.Tasks.slow_down()
reserved = await video.video.Camera.reserve(motion.historic.Historic, timeout=5, suspension=10)
try:
if reserved:
await motion.historic.Historic.acquire()
await response.send_file(tools.strings.tostrings(request.path[len("/historic/download/"):]), base64=False)
else:
await response.send_not_found()
finally:
if reserved:
await motion.historic.Historic.release()
await video.video.Camera.unreserve(motion.historic.Historic)
Functions
async def download_image(request, response, args)-
Download historic image
Expand source code
@server.httpserver.HttpServer.add_route(b'/historic/download/.*', available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion) async def download_image(request, response, args): """ Download historic image """ tools.tasking.Tasks.slow_down() reserved = await video.video.Camera.reserve(motion.historic.Historic, timeout=5, suspension=10) try: if reserved: await motion.historic.Historic.acquire() await response.send_file(tools.strings.tostrings(request.path[len("/historic/download/"):]), base64=False) else: await response.send_not_found() finally: if reserved: await motion.historic.Historic.release() await video.video.Camera.unreserve(motion.historic.Historic) def get_days_pagination(last_days, request)-
Get the pagination html part of days
Expand source code
def get_days_pagination(last_days, request): """ Get the pagination html part of days """ current_day = b"" last_days = list(last_days) if len(last_days) > 0: last_days.sort() last_days.reverse() pages = [] day_id = int(request.params.get(b"day_id",0)) max_days = 5 if len(last_days) > max_days: if day_id - 2 < 0: begin_day = 0 end_day = len(last_days) if len(last_days) < max_days else max_days elif day_id + 3 > len(last_days): end_day = len(last_days) begin_day = 0 if len(last_days) < max_days else len(last_days) - max_days else: begin_day = day_id - max_days // 2 end_day = day_id + (max_days//2 + 1) if begin_day > 0: pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(begin_day-1))) else: begin_day = 0 end_day = len(last_days) i = begin_day for day in last_days[begin_day:end_day]: pages.append(PageItem(text=day[8:10], class_=b"", active=b"active" if day_id == i else b"", href=b"/historic?day_id=%d"%i if day_id != i else b"")) if day_id == i: current_day = day i += 1 if len(last_days) > max_days: if end_day < len(last_days): pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(end_day))) pagination_begin = Pagination(pages, class_=b"pagination-sm", id=b"pagination_begin") pagination_end = Pagination(pages, class_=b"pagination-sm", id=b"pagination_end") result = pagination_begin, pagination_end,current_day else: result = None,None,current_day return result async def historic(request, response, args)-
motion.historic.Historic motion detection page
Expand source code
@server.httpserver.HttpServer.add_route(b'/historic', menu=tools.lang.menu_motion, item=tools.lang.item_historic, available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion) async def historic(request, response, args): """ motion.historic.Historic motion detection page """ webpage.streamingpage.Streaming.stop() motion.historic.Historic.get_root() last_days = await motion.historic.Historic.get_last_days() pagination_begin, pagination_end,current_day = get_days_pagination(last_days, request) if pagination_end is not None and pagination_begin is not None: page_content = [\ pagination_begin, Tag(b""" <div class="modal" id="zoom_window"> <div class="modal-dialog modal-fullscreen"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body" > <canvas id="zoom_image" class="w-100" data-bs-dismiss="modal"/> </div> </div> </div> </div> <script type='text/javascript'> window.onload = load_historic; var current_day = '%s'; var historic = null; var last_id = 0; var historic_request = new XMLHttpRequest(); var image_request = new XMLHttpRequest(); const MOTION_FILENAME =0; const MOTION_WIDTH =1; const MOTION_HEIGHT =2; const MOTION_DIFFS =3; const MOTION_SQUAREX =4; const MOTION_SQUAREY =5; function load_historic() { historic_request.onreadystatechange = historic_loaded; historic_request.open("GET","historic/historic.json",true); historic_request.send(); } function historic_loaded() { if (historic_request.readyState === XMLHttpRequest.DONE) { if (historic_request.status === 200) { historic = JSON.parse(historic_request.responseText); select_day(); load_image(); } } } function load_image() { if (historic.length > 0) { var motion = historic[last_id]; image_request.onreadystatechange = image_loaded; image_request.open("GET","/historic/images/" + motion[MOTION_FILENAME],true); image_request.send(); } } function rtrim(x, characters) { var start = 0; var end = x.length - 1; while (characters.indexOf(x[end]) >= 0) { end -= 1; } return x.substr(0, end + 1); } function ltrim(x, characters) { var start = 0; while (characters.indexOf(x[start]) >= 0) { start += 1; } var end = x.length - 1; return x.substr(start); } function get_day(id) { var filename = historic[id][MOTION_FILENAME]; filename = ltrim(filename, "/"); filename = filename.split("/"); return filename[1]+"/"+filename[2]+"/"+filename[3]; } function select_day() { if (historic.length > 0) { for (i = 0; i < historic.length; i++) { if (get_day(i) == current_day) { last_id = i; break; } } } } function get_quality() { if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) { return 1; } else { return 2; } } function image_loaded() { if (image_request.readyState === XMLHttpRequest.DONE) { if (image_request.status === 200) { var motion = historic[last_id]; var div = document.createElement("div"); div.className = "col-lg-2 pb-1"; var canvas = document.createElement("canvas"); canvas.width = motion[MOTION_WIDTH ] * get_quality(); canvas.height = motion[MOTION_HEIGHT] * get_quality(); canvas.id = last_id; canvas.className = "w-100"; canvas.setAttribute("data-bs-toggle","modal"); canvas.setAttribute("data-bs-target","#zoom_window"); canvas.onclick = e => { var view = document.getElementById('zoom_image'); var destCtx = view.getContext('2d'); view.width = motion[MOTION_WIDTH ] * get_quality(); view.height = motion[MOTION_HEIGHT] * get_quality(); destCtx.drawImage(canvas, 0, 0); }; var image = new Image(); image.src = 'data:image/jpeg;base64,' + image_request.response; image.onload = function(){show_motion(canvas.id, image);}; div.appendChild(canvas); if (last_id == 0) { document.getElementById('motions').replaceChildren(div); } else { document.getElementById('motions').appendChild(div); } last_id = last_id + 1; if (last_id < historic.length-1) { if (get_day(last_id) == current_day) { setTimeout(load_image, 1); } } } else { setTimeout(load_image, 1); } } } function get_difference(motion, x, y) { var squarex = motion[MOTION_SQUAREX]; var maxx = motion[MOTION_WIDTH] /squarex; var bitpos = y*maxx + x; if (typeof motion[MOTION_DIFFS] === 'string') { return motion[MOTION_DIFFS][bitpos]; } else { var word = parseInt(bitpos/32); var bit = 31-bitpos%%32; var mask = 1 << bit; var val = motion[MOTION_DIFFS][word]; if (val & mask) { return "#"; } else { return " "; } } } function show_motion(id, image) { var x; var y; var motion = historic[id]; var canvas = document.getElementById(id); var ctx = canvas.getContext('2d'); var squarex = motion[MOTION_SQUAREX] * get_quality(); var squarey = motion[MOTION_SQUAREY] * get_quality(); var maxx = (motion[MOTION_WIDTH] /squarex) * get_quality(); var maxy = (motion[MOTION_HEIGHT]/squarey) * get_quality(); ctx.drawImage(image, 0, 0, motion[MOTION_WIDTH ] , motion[MOTION_HEIGHT], 0, 0, motion[MOTION_WIDTH ] * get_quality(), motion[MOTION_HEIGHT] * get_quality()); ctx.strokeStyle = "red"; ctx.lineWidth = 1 * get_quality(); for (y = 0; y < maxy; y ++) { for (x = 0; x < maxx; x ++) { var detection = get_difference(motion, x, y); if (x >= 1) { var previous = get_difference(motion, x-1, y); if (previous != detection) { ctx.beginPath(); ctx.moveTo(0 + x*squarex, 0 + y*squarey); ctx.lineTo(0 + x*squarex, 0 + y*squarey + squarey); ctx.stroke(); } } } } for (x = 0; x < maxx; x ++) { for (y = 0; y < maxy; y ++) { var detection = get_difference(motion, x, y); if (y >= 1) { var previous = get_difference(motion, x, y-1); if (previous != detection) { ctx.beginPath(); ctx.moveTo(0 + x*squarex, 0 + y*squarey); ctx.lineTo(0 + x*squarex + squarex, 0 + y*squarey); ctx.stroke(); } } } } var font_size = 35 * get_quality(); ctx.font = font_size + "px monospace"; var width = ctx.measureText(get_name(motion[MOTION_FILENAME])).width; ctx.fillStyle = 'rgba(0,0,0,0.3)'; ctx.fillRect(0,(motion[MOTION_HEIGHT] * get_quality())-font_size, width+get_quality(), font_size+10); ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fillText(get_name(motion[MOTION_FILENAME]), 3, (motion[MOTION_HEIGHT] * get_quality() - 5*get_quality() )); } // Convert the filename into text displayed function get_name(filename) { filename = filename.split(".")[0]; lst = filename.split("/"); filename = lst[lst.length-1]; filename = filename.replace("D= ","D="); spl = filename.split(" "); if (spl.length == 3) { date = spl[0].split("_")[0]; hour = spl[0].split("_")[1]; date = date.replaceAll("-","/") + " " + hour.replaceAll("-",":"); last = spl[1] + " " + spl[2]; result = date + " "+ last; } else { result = filename; } return result; } function get_date(filename) { return get_name(filename).substring(0,10); } </script> <div id="motions" class="row"></div> """%(current_day)), Br(), pagination_end ] else: page_content = Tag(b"<span>%s</span>"%tools.lang.historic_not_available) page = webpage.mainpage.main_frame(request, response, args,tools.lang.last_motion_detections,Form(page_content)) await response.send_page(page) async def historic_image(request, response, args)-
Send historic image
Expand source code
@server.httpserver.HttpServer.add_route(b'/historic/images/.*', available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion) async def historic_image(request, response, args): """ Send historic image """ tools.tasking.Tasks.slow_down() reserved = await video.video.Camera.reserve(motion.historic.Historic, timeout=5, suspension=10) try: if reserved: await motion.historic.Historic.acquire() await response.send_file(tools.strings.tostrings(request.path[len("/historic/images/"):]), base64=True) else: await response.send_not_found() finally: if reserved: await motion.historic.Historic.release() await video.video.Camera.unreserve(motion.historic.Historic) async def historic_json(request, response, args)-
Send historic json file
Expand source code
@server.httpserver.HttpServer.add_route(b'/historic/historic.json', available=tools.info.iscamera() and video.video.Camera.is_activated() and tools.features.features.motion) async def historic_json(request, response, args): """ Send historic json file """ tools.tasking.Tasks.slow_down() try: await response.send_buffer(b"historic.json", await motion.historic.Historic.get_json()) except Exception as err: await response.send_not_found(err)