410 lines
18 KiB
HTML
410 lines
18 KiB
HTML
{% extends "base.html" %} {% block main_content %}
|
|
{# make the form-switch / toggle info color set, give or take #}
|
|
<style>
|
|
.norm-txt { font-size: 1.0rem }
|
|
.form-check-input:checked {
|
|
background-color: #39C0ED;
|
|
border-color: #CFF4FC;
|
|
}
|
|
.form-switch .form-check-input {
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2339C0ED'/%3e%3c/svg%3e");
|
|
}
|
|
.form-switch .form-check-input:focus {
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23CFF4FC'/%3e%3c/svg%3e");
|
|
}
|
|
</style>
|
|
|
|
<script src="{{ url_for( 'internal', filename='js/view_transform.js')}}"></script>
|
|
|
|
<script>
|
|
var gap=0.8
|
|
var grayscale=0
|
|
var throbber=0
|
|
|
|
var objs=[]
|
|
var current={{current}}
|
|
var eids="{{eids}}"
|
|
var eid_lst=eids.split(",")
|
|
|
|
{% for id in objs %}
|
|
e=new Object()
|
|
e.name = "{{objs[id].name}}"
|
|
e.url = "{{objs[id].FullPathOnFS()}}"
|
|
e.type = "{{objs[id].type.name}}"
|
|
{% if objs[id].file_details.faces %}
|
|
e.face_model="{{objs[id].file_details.faces[0].facefile_lnk.model_used}}"
|
|
{% endif %}
|
|
e.faces=[]
|
|
{% for face in objs[id].file_details.faces %}
|
|
data = {
|
|
'x': '{{face.locn[3]}}', 'y': '{{face.locn[0]}}',
|
|
'w': '{{face.locn[1]-face.locn[3]}}', 'h':'{{face.locn[2]-face.locn[0]}}'
|
|
}
|
|
{% if face.refimg %}
|
|
data['who']='{{face.refimg.person.tag}}'
|
|
data['distance']="{{face.refimg_lnk.face_distance|round(2)}}"
|
|
{% endif %}
|
|
e.faces.push( data )
|
|
{% endfor %}
|
|
objs[{{id}}]=e
|
|
{% endfor %}
|
|
|
|
function NewWidth()
|
|
{
|
|
w_r=im.width/(window.innerWidth*gap)
|
|
h_r=im.height/(window.innerHeight*gap)
|
|
if( w_r > h_r )
|
|
return window.innerWidth*gap
|
|
else
|
|
return im.width*gap / (im.height/window.innerHeight)
|
|
}
|
|
|
|
function NewHeight()
|
|
{
|
|
w_r=im.width/(window.innerWidth*gap)
|
|
h_r=im.height/(window.innerHeight*gap)
|
|
if( h_r > w_r )
|
|
return window.innerHeight*gap
|
|
else
|
|
return im.height*gap / (im.width/window.innerWidth)
|
|
}
|
|
|
|
// Define this once and before it will be called, hence at the top of this file
|
|
function DrawImg()
|
|
{
|
|
// another call to this func will occur on load, so skip this one
|
|
if( im.width == 0 )
|
|
return
|
|
|
|
canvas.width=NewWidth(im)
|
|
canvas.height=NewHeight(im)
|
|
|
|
// actually draw the pixel images to the canvas at the right size
|
|
if( grayscale )
|
|
context.filter='grayscale(1)'
|
|
context.drawImage(im, 0, 0, canvas.width, canvas.height )
|
|
// -50 is a straight up hack, no idea why this works, but its good enough for me
|
|
if( throbber )
|
|
$('#throbber').attr('style', 'display:show; position:absolute; left:'+canvas.width/2+'px; top:'+(canvas.height/2-50)+'px' )
|
|
else
|
|
$('#throbber').hide();
|
|
|
|
|
|
// show (or not) the whole figcaption with fname in it - based on state of fname_toggle
|
|
if( $('#fname_toggle').prop('checked' ) )
|
|
{
|
|
$('.figcaption').attr('style', 'display:show' )
|
|
// reset fname for new image (if navigated left/right to get here)
|
|
$('#fname').html(objs[current].name)
|
|
}
|
|
else
|
|
$('.figcaption').attr('style', 'display:none' )
|
|
|
|
// if we have faces, the enable the toggles, otherwise disable them
|
|
// and reset model select too
|
|
if( objs[current].faces.length )
|
|
{
|
|
$('#faces').attr('disabled', false)
|
|
$('#distance').attr('disabled', false)
|
|
$('#model').val( Number(objs[current].face_model) )
|
|
}
|
|
else
|
|
{
|
|
$('#faces').attr('disabled', true)
|
|
$('#distance').attr('disabled', true)
|
|
// if no faces, then model is N/A (always 1st element - or 0 in select)
|
|
$('#model').val(0)
|
|
}
|
|
|
|
// okay, we want faces drawn so lets do it
|
|
if( $('#faces').prop('checked') )
|
|
{
|
|
// draw rect on each face
|
|
for( i=0; i<objs[current].faces.length; i++ )
|
|
{
|
|
x = objs[current].faces[i].x / ( im.width/canvas.width )
|
|
y = objs[current].faces[i].y / ( im.height/canvas.height )
|
|
w = objs[current].faces[i].w / ( im.width/canvas.width )
|
|
h = objs[current].faces[i].h / ( im.height/canvas.height )
|
|
context.beginPath()
|
|
context.rect( x, y, w, h )
|
|
context.lineWidth = 2
|
|
context.strokeStyle = 'green'
|
|
if( objs[current].faces[i].who )
|
|
{
|
|
// finish face box, need to clear out new settings for
|
|
// transparent backed-name tag
|
|
context.stroke();
|
|
context.beginPath()
|
|
context.lineWidth = 0.1
|
|
context.font = "30px Arial"
|
|
context.globalAlpha = 0.6
|
|
str=objs[current].faces[i].who
|
|
if( $('#distance').prop('checked') )
|
|
str += "("+objs[current].faces[i].distance+")"
|
|
|
|
bbox = context.measureText(str);
|
|
f_h=bbox.fontBoundingBoxAscent
|
|
if( bbox.fontBoundingBoxDescent )
|
|
f_h += bbox.fontBoundingBoxDescent
|
|
f_h -= 8
|
|
context.rect( x+w/2-bbox.width/2, y-f_h, bbox.width, f_h )
|
|
context.fillStyle="white"
|
|
context.fill()
|
|
context.stroke();
|
|
context.beginPath()
|
|
context.globalAlpha = 1.0
|
|
context.font = "30px Arial"
|
|
context.textAlign = "center"
|
|
context.fillStyle = "green"
|
|
context.fillText(str, x+w/2, y-2)
|
|
}
|
|
/* can use to show lower left coords of a face for debugging
|
|
else
|
|
{
|
|
context.font = "14px Arial"
|
|
context.textAlign = "center"
|
|
context.fillStyle = "black"
|
|
context.fillText( 'x=' + objs[current].faces[i].x + ', y=' + objs[current].faces[i].y, x+w/2, y-2)
|
|
context.fillText( 'x=' + objs[current].faces[i].x + ', y=' + objs[current].faces[i].y, x+w/2, y-2)
|
|
}
|
|
*/
|
|
context.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
function ResizeVideo()
|
|
{
|
|
$('#video').height(window.innerHeight*gap)
|
|
}
|
|
|
|
function FaceToggle()
|
|
{
|
|
$('#distance').prop('disabled', function(i, v) { return !v; });
|
|
DrawImg()
|
|
}
|
|
|
|
function CallViewListRoute(dir)
|
|
{
|
|
s='<form id="_fmv" method="POST" action="/viewlist">'
|
|
s+='<input type="hidden" name="eids" value="'+$("#eids").val() + '">'
|
|
s+='<input type="hidden" name="noo" value="{{OPT['noo']}}">'
|
|
s+='<input type="hidden" name="cwd" value="{{OPT['cwd']}}">'
|
|
s+='<input type="hidden" name="root" value="{{OPT['root']}}">'
|
|
s+='<input type="hidden" name="size" value="{{OPT['size']}}">'
|
|
s+='<input type="hidden" name="grouping" value="{{OPT['grouping']}}">'
|
|
s+='<input type="hidden" name="offset" value="{{OPT['offset']}}">'
|
|
s+='<input type="hidden" name="folders" value="{{OPT['folders']}}">'
|
|
s+='<input type="hidden" name="how_many" value="{{OPT['how_many']}}">'
|
|
s+='<input type="hidden" name="orig_url" value="{{request.path}}">'
|
|
s+='<input type="hidden" name="' + dir + '" value="1">'
|
|
{% if search_term is defined %}
|
|
s+='<input type="hidden" name="search_term" value="{{search_term}}">'
|
|
{% endif %}
|
|
s+='</form>'
|
|
$(s).appendTo('body')
|
|
$('#_fmv').submit();
|
|
}
|
|
|
|
function ViewImageOrVideo()
|
|
{
|
|
if( objs[current].type == 'Image' )
|
|
{
|
|
im.src='http://mara.ddp.net:5000/' + objs[current].url
|
|
$('#video').hide()
|
|
$('#figure').show()
|
|
if( fullscreen )
|
|
{
|
|
console.log('going fs on image')
|
|
$('#canvas').get(0).requestFullscreen()
|
|
}
|
|
}
|
|
if( objs[current].type == 'Video' )
|
|
{
|
|
$('#video').prop('src', 'http://mara.ddp.net:5000/' + objs[current].url )
|
|
$('#figure').hide()
|
|
$('#video').show()
|
|
if( fullscreen )
|
|
{
|
|
console.log('going fs on video')
|
|
$('#video').get(0).requestFullscreen()
|
|
}
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|
|
<div id="viewer" class="container-fluid">
|
|
|
|
{% set max=eids.split(',')|length %}
|
|
<input type="hidden" name="eids" value={{eids}}>
|
|
<div class="row">
|
|
<button title="Show previous image" class="col-auto btn btn-outline-info px-2" style="padding: 10%" id="la"
|
|
{% if OPT['offset'] == 0 and eids.find(current|string) == 0 %}
|
|
disabled
|
|
{% endif %}
|
|
onClick="
|
|
if( document.fullscreen == false )
|
|
fullscreen = false
|
|
cidx = eid_lst.indexOf(current.toString())
|
|
prev=cidx-1
|
|
if( prev < 0 )
|
|
{
|
|
if( {{OPT['offset']}} )
|
|
{
|
|
CallViewListRoute('prev')
|
|
return
|
|
}
|
|
else
|
|
{
|
|
$('#la').attr('disabled', true )
|
|
prev=0
|
|
}
|
|
}
|
|
$('#ra').attr('disabled', false )
|
|
current=eid_lst[prev]
|
|
ViewImageOrVideo()
|
|
">
|
|
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
|
</button>
|
|
<figure class="col col-auto border border-info rounded m-0 p-1" id="figure">
|
|
<canvas id="canvas"></canvas>
|
|
<img id="throbber" src="{{url_for('internal', filename='throbber.gif')}}" style="display:none;">
|
|
<script>
|
|
var im=new Image();
|
|
im.onload=DrawImg
|
|
im.src="http://mara.ddp.net:5000/" + objs[current].url
|
|
var context = canvas.getContext('2d')
|
|
window.addEventListener('resize', DrawImg, false);
|
|
</script>
|
|
<figcaption class="figure-caption text-center text-wrap text-break"><span id="fname">{{objs[current].name}}</span></figcaption>
|
|
</figure>
|
|
{% if objs[current].type.name != "Image" %}
|
|
<script>$('#figure').attr('style', 'display:none')</script>
|
|
{% endif %}
|
|
<video id="video" class="col col-auto" controls>
|
|
<source src="http://mara.ddp.net:5000/{{objs[current].FullPathOnFS()}}" type="video/mp4">
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
<script>
|
|
window.addEventListener('resize', ResizeVideo, false);
|
|
ResizeVideo()
|
|
{% if objs[current].type.name != "Video" %}
|
|
$('#video').attr('style', 'display:none')
|
|
{% endif %}
|
|
</script>
|
|
|
|
<button title="Show next image" class="col-auto btn btn-outline-info px-2" style="padding: 10%" id="ra"
|
|
onClick="
|
|
{% if 'last_entry_in_db' in OPT %}
|
|
if( current == {{OPT['last_entry_in_db']}} )
|
|
{
|
|
$('#ra').attr('disabled', true )
|
|
return
|
|
}
|
|
{% endif %}
|
|
if( document.fullscreen == false )
|
|
fullscreen = false
|
|
cidx = eid_lst.indexOf(current.toString())
|
|
if( cidx < eid_lst.length-1 )
|
|
{
|
|
current=eid_lst[cidx+1]
|
|
ViewImageOrVideo()
|
|
$('#la').attr('disabled', false )
|
|
}
|
|
else
|
|
{
|
|
{# only go next route if list contains as many elements as we asked to display... can be more than how_many on any page in reality, as its really how_many per dir? #}
|
|
if( eid_lst.length >= {{OPT['how_many']}} )
|
|
CallViewListRoute('next')
|
|
}
|
|
">
|
|
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
|
</button>
|
|
</div id="/form-row">
|
|
{# use this for color of toggles: https://www.codeply.com/p/4sL9uhevwJ #}
|
|
<div class="row">
|
|
{# this whole div, just takes up the same space as the left button and is hidden for alignment only #}
|
|
<div class="col-auto px-0">
|
|
<button class="btn btn-outline-info px-2" disabled style="visibility:hidden">
|
|
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
|
</button>
|
|
</div>
|
|
<span class="col-auto my-auto">Show:</span>
|
|
<div title="Toggle showing filename" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
|
<input class="form-check-input" type="checkbox" id="fname_toggle" onChange="$('.figure-caption').toggle()" checked>
|
|
<label class="form-check-label ps-1" for="fname_toggle">Filename</label>
|
|
</div>
|
|
<div title="Toggle showing matched faces" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
|
<input class="form-check-input" type="checkbox" onChange="FaceToggle()" id="faces">
|
|
<label class="form-check-label ps-1" for="faces">Faces</label>
|
|
</div>
|
|
<div title="Toggle showing 'distance' on matched faces" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
|
<input class="form-check-input" type="checkbox" onChange="DrawImg()" id="distance">
|
|
<label class="form-check-label ps-1" for="distance">Distance</label>
|
|
</div>
|
|
<div title="Change the model used to detect faces" class="col col-auto my-auto">
|
|
AI Model:
|
|
{# can use 0 as default, it will be (re)set correctly in DrawImg() anyway #}
|
|
{{CreateSelect( "model", 0, ["N/A", "normal", "slow/accurate"], "", "rounded norm-txt", [0,1,2])|safe }}
|
|
</div>
|
|
<div class="col col-auto pt-1">
|
|
<button class="btn btn-outline-info p-1" title="Rotate by 90 degrees" onClick="Transform(90)">
|
|
<svg width="28" height="28" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#rot90"/></svg>
|
|
</button>
|
|
<button class="btn btn-outline-info p-1" title="Rotate by 180 degrees" onClick="Transform(180)">
|
|
<svg width="28" height="28" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#rot180"/></svg>
|
|
</button>
|
|
<button class="btn btn-outline-info p-1" title="Rotate by 270 degrees" onClick="Transform(270)">
|
|
<svg width="28" height="28" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#rot270"/></svg>
|
|
</button>
|
|
<button class="btn btn-outline-info p-1" title="Flip horizontally" onClick="Transform('fliph')">
|
|
<svg width="28" height="28" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#flip_h"/></svg>
|
|
</button>
|
|
<button class="btn btn-outline-info p-1" title="Flip vertically" onClick="Transform('flipv')">
|
|
<svg width="28" height="28" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#flip_v"/></svg>
|
|
</button>
|
|
<button class="btn btn-outline-info p-1" title="View in Fullscreen mode" onClick="fullscreen=true; ViewImageOrVideo()">
|
|
<svg width="28" height="28" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#fullscreen"/></svg>
|
|
</button>
|
|
</div>
|
|
</div class="row">
|
|
{% endblock main_content %}
|
|
{% block script_content %}
|
|
<script>
|
|
$( document ).keydown(function(event) {
|
|
switch (event.key)
|
|
{
|
|
case "Left": // IE/Edge specific value
|
|
case "ArrowLeft":
|
|
if( $('#la').prop('disabled') == false )
|
|
$('#la').click()
|
|
break;
|
|
case "Right": // IE/Edge specific value
|
|
case "ArrowRight":
|
|
if( $('#ra').prop('disabled') == false )
|
|
$('#ra').click()
|
|
break;
|
|
case "d":
|
|
$('#distance').click()
|
|
break;
|
|
case "f":
|
|
$('#faces').click()
|
|
break;
|
|
case "n":
|
|
$('#fname_toggle').click()
|
|
break;
|
|
default:
|
|
return; // Quit when this doesn't handle the key event.
|
|
}
|
|
});
|
|
|
|
var fullscreen=false;
|
|
{% if OPT['fullscreen']=='true' %}
|
|
fullscreen=true;
|
|
ViewImageOrVideo()
|
|
{% endif %}
|
|
</script>
|
|
{% endblock script_content %}
|