added webinterface

This commit is contained in:
Florian 2014-03-02 14:22:10 +03:00
parent 08feea56c8
commit c35a1c1abb
25 changed files with 738 additions and 6 deletions

1
public/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.swp

46
public/README Normal file
View File

@ -0,0 +1,46 @@
This Interface is developed to work with firefox 27.0.1.
Installation
============
1. Download mjpegplexer:
http://git.sublab.org/mjpegplexer/
2. Setup mjpegplexer properly.
3. Copy the contents of cctv-control to the root
of your webserver and set the camera-host-IP
to the server that runs mjpegplexer.
Omit Printing-Dialogue
======================
To make firefox not display the printing dialogue a config
option needs to be added. I recommend to print to a file,
for everything to work as expected.
1. enter "about:config" and hit enter
2. create a new boolean (right-click)
with the name "print.always_silent_print"
and set it to "true"
3. set "print.print_to_file" to "true"
4. set your preferred filename in
"print.print_to_filename"
5.(opt) adapt values of footers and headers for your needs
Default Values
==============
print.always_silent_print false
print.print_to_file false
print.footerleft &PT //Pagenumber
print.footerright &D //Date
print.headerleft &T //?
print.headerright &U //Filename

32
public/cam.html Normal file
View File

@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html lang=en>
<head>
<meta charset="utf-8">
<script src="js/cctv-control.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="css/cam.css">
</head>
<body onkeydown="keyDown(event)" id='body'>
<div id="screen">
<img src="img/testbild.gif" id="camImage">
</div>
<div id="form">
<img src="about:blank" id="formImage">
<p>
</p>
<p>Personalkennzeichnung</p>
<input id="formName"><br>
<p>Zeitpunkt</p>
<input id="formDate" readonly><br>
<p>Analyse der Aufnahme</p>
<textarea id="formAnalysis1"></textarea>
<p>Bemerkungen zum Untersuchungsverlauf</p>
<textarea id="formAnalysis2"></textarea>
<p>Pers&ouml;nliche Bemerkungen</p>
<textarea id="formAnalysis3"></textarea>
<p>
<a href="#" id='printButton' onclick='hideForm()'>&nbsp</a>
</p>
</div>
</body>
</html>

82
public/css/cam.css Normal file
View File

@ -0,0 +1,82 @@
body {
font-family: Helvetica, Verdana, Arial, sans-serif;
background-color: #000;
padding: 0px;
margin: 0px;
}
.camNumber {
position: fixed;
left: 20px;
top: -100px;
background: rgba(0,0,0,0.6);
width: 100px;
text-align: center;
color: rgba(255, 255, 255, 0.7);
font-weight: bold;
font-family: Helvetica, Verdana, Arial, sans-serif;
font-size: 120px;
line-height: 120px;
z-index: 100;
}
#camImage {
min-height: 100%;
min-width: 1024px;
width: 100%;
height: auto;
position: fixed;
top: 0;
left: 0;
margin: 0;
padding: 0;
z-index: 50;
}
#form {
/*visibility: hidden;*/
width: 800px;
height: 100%;
background-color: rgba(127, 192, 255, 0.9);
margin: 0 auto;
position: relative;
top: 0px;
padding: 40px;
z-index: 200;
}
#formImage {
margin-right: 0px;
margin-left: auto;
float: right;
position: relative;
width: 400px;
}
#printButton {
width: 200px;
height: 100px;
display: block;
margin: 10px auto;
background-image: url('../img/print_button.png');
text-decoration: none;
}
#printButton:hover {
background-image: url('../img/print_button_highlight.png');
}

24
public/css/print.css Normal file
View File

@ -0,0 +1,24 @@
#page {
width: 800px;
}
#camImage {
position: absolute;
top: 0px;
left: 0px;
}
#metaContainer {
position: absolute;
top: 0px;
left: 700px;
}
#textContainer {
position: absolute;
top: 500px;
left: 0px;
}

65
public/img/arrow.svg Normal file
View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="arrow.svg"
inkscape:export-filename="/home/olf/Projekte/Kunstprojekt-CCTV/js-interface/down.png"
inkscape:export-xdpi="99.989998"
inkscape:export-ydpi="99.989998">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.959798"
inkscape:cx="72.885621"
inkscape:cy="46.110712"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1280"
inkscape:window-height="763"
inkscape:window-x="0"
inkscape:window-y="16"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-952.36218)">
<path
style="fill:#000000;fill-opacity:1;stroke:none"
d="m 49.999995,1042.3622 40.00001,-80.00001 -80.00001,0 z"
id="path2985"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/img/down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

BIN
public/img/left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

BIN
public/img/print_button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

120
public/img/print_button.svg Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="201"
height="101"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="print_button.svg"
inkscape:export-filename="/home/olf/Projekte/Kunstprojekt-CCTV/js-interface/img/print_button_highlight.png"
inkscape:export-xdpi="89.10891"
inkscape:export-ydpi="89.10891">
<defs
id="defs4">
<linearGradient
id="linearGradient3765">
<stop
id="stop3767"
offset="0"
style="stop-color:#666666;stop-opacity:1;" />
<stop
id="stop3769"
offset="1"
style="stop-color:#c0c0c0;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3755">
<stop
style="stop-color:#666666;stop-opacity:1;"
offset="0"
id="stop3757" />
<stop
id="stop3771"
offset="0.56964296"
style="stop-color:#939393;stop-opacity:1;" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3759" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3755"
id="linearGradient3761"
x1="197.5"
y1="297.00504"
x2="196.78574"
y2="169.14789"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="38.008931"
inkscape:cy="68.443537"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1280"
inkscape:window-height="763"
inkscape:window-x="0"
inkscape:window-y="16"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-88.0625,-169)">
<rect
style="fill:url(#linearGradient3761);fill-opacity:1;stroke:#000000;stroke-opacity:1"
id="rect2985"
width="200"
height="100"
x="88.571426"
y="169.50504"
ry="18.571428" />
<text
xml:space="preserve"
style="font-size:120px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Open Sans;-inkscape-font-specification:Open Sans"
x="109.69447"
y="230.50504"
id="text3763"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3765"
x="109.69447"
y="230.50504"
style="font-size:40px;fill:#ffffff">Drucken</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/img/right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

BIN
public/img/testbild-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

BIN
public/img/testbild-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 KiB

BIN
public/img/testbild-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 KiB

BIN
public/img/testbild-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 KiB

BIN
public/img/testbild-4.xcf Normal file

Binary file not shown.

BIN
public/img/testbild.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
public/img/testbild.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

BIN
public/img/testbild.xcf Normal file

Binary file not shown.

BIN
public/img/up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

244
public/js/cctv-control.js Normal file
View File

@ -0,0 +1,244 @@
// CCTV Control
//
// Author: Florian Raemisch
// Email: olf@subsignal.org
//
// cctv-control is a webinterface to control and view
// mjpeg encoded videos by cctv cameras which are
// proxied by mjpegplexer. camera control is possible
// with arrow-keys, if mjpegplexer is configured
// properly.
//
// This Software is licensed under the GPL Version 3, 29 June 2007
var cam = 0;
var camHost = "127.0.0.1:8080";
var controlHost = "127.0.0.1:8080";
var formActive = 0; //crazy errors if boolean is used here...
//create event which is triggered when DOM is ready
window.onDomReady = initReady;
//change from testbild to default cam when DOM is ready
window.onDomReady(initViewer);
function initReady(fn) {
if(document.addEventListener) {
document.addEventListener("DOMContentLoaded", fn, false);
}
}
//hide the form, and switch on camera
function initViewer() {
var display = document.getElementById('screen');
var tempCam = parseInt(localStorage.getItem('cam'));
formActive = localStorage.getItem("formActive");
if (tempCam <= 7 && tempCam >= 0) {
cam = tempCam;
}
//if form was displayed, when page was reloaded, display form
if (formActive == 1) {
displayCamNumber();
display.innerHTML += '<img id="camImage" src="http://' + camHost + '/cam/' + cam + '/stream.mjpeg" />';
showForm();
}
else {
formActive == 0;
document.getElementById('form').style.visibility = "hidden";
changeCam();
}
}
function displayCamNumber() {
var display = document.getElementById('screen');
camDisplay = cam + 1;
display.innerHTML = '<p class="camNumber">' + camDisplay + '</p>';
}
//change currently displayed camera to the one in cam variable
function changeCam() {
var display = document.getElementById('screen');
if (cam <= 7 && cam >= 0) {
displayCamNumber();
display.innerHTML += '<img id="camImage" src="http://' + camHost + '/cam/' + cam + '/stream.mjpeg" />';
localStorage.setItem("cam", cam);
}
else {
display.innerHTML = '<img id="camImage" src="img/testbild.gif" />';
}
}
//send control command to currently selected camera
function controlCam(direction) {
if (direction == "left" || direction == "right" || direction == "up" || direction == "down") {
var request = new XMLHttpRequest();
request.open("get", "http://" + controlHost + "/cam/" + cam + "/control/" + direction, false);
request.send();
return false;
}
}
//save current screenshot in local storage
function saveImage() {
var storageFiles = {}, //local storage object
xhr = new XMLHttpRequest(),
blob,
fileReader = new FileReader(),
snapShot = new Image(), //dummy-object to display snapshot
imgCanvas = document.createElement("canvas"),
imgContext = imgCanvas.getContext("2d");
//xhr.open("GET", 'img/testbild-' + camDisplay + '.png', true);
xhr.open("GET", 'http://' + camHost + '/cam/' + cam + '/snapshot.jpg', true);
xhr.responseType = "arraybuffer";
xhr.addEventListener("load", function () {
if (xhr.status === 200) {
blob = new Blob([xhr.response], {type: "image/jpg"});
fileReader.onload = function (evt) {
var result = evt.target.result;
snapShot.src = result;
try {
localStorage.setItem("snapShot", result);
}
catch (e) {
console.log("Storage failed: " + e);
}
};
fileReader.readAsDataURL(blob);
fileReader.onloadend = function() {
showForm();
}
}
}, false);
xhr.send();
}
function showForm() {
//load snapshot from localstorage
var display1 = document.getElementById("camImage"),
display2 = document.getElementById("formImage"),
stor = localStorage.getItem("snapShot");
var d = new Date();
var date = d.getDay();
date += '/';
date += d.getMonth();
date += '/';
date += d.getFullYear();
var time = d.getHours();
time += ':';
time += d.getMinutes();
time += ':';
time += d.getSeconds();
time += ' Uhr';
//display loaded image
if (stor) {
display1.setAttribute("src", stor);
display2.setAttribute("src", stor);
}
//insert date in date-field
document.getElementById("formDate").value = date + ' ' + time;
//make form visible
document.getElementById("form").style.visibility = 'visible';
formActive = 1;
localStorage.setItem("formActive", formActive);
}
function hideForm() {
//save form data in local storage for later use
localStorage.setItem("formName", document.getElementById('formName').value);
localStorage.setItem("formDate", document.getElementById('formDate').value);
localStorage.setItem("formAnalysis1", document.getElementById('formAnalysis1').value);
localStorage.setItem("formAnalysis2", document.getElementById('formAnalysis2').value);
localStorage.setItem("formAnalysis3", document.getElementById('formAnalysis3').value);
//hide form and unfocus button
//otherwise next enter press will trigger print-function
document.getElementById('printButton').blur();
window.open('print.html', 'Printlayout');
document.getElementById("form").style.visibility = 'hidden';
formActive = 0;
localStorage.setItem("formActive", formActive);
changeCam();
}
function keyDown(event) {
// chaning the camera is only possible if the form is not displayed
if (formActive != 1) {
//numpad has different keycodes!
switch(event.keyCode) {
case 49: // 1
cam = 0;
changeCam();
break;
case 50: // 2
cam = 1;
changeCam();
break;
case 51: // 3
cam = 2;
changeCam();
break;
case 52: // 4
cam = 3;
changeCam();
break;
case 53: // 5
cam = 4;
changeCam();
break;
case 54: // 6
cam = 5;
changeCam();
break;
case 55: // 7
cam = 6;
changeCam();
break;
case 56: // 8
cam = 7;
changeCam();
break;
case 37: // arrow left
controlCam("left");
break;
case 38: // arrow up
controlCam("up");
break;
case 39: // arrow right
controlCam("right");
break;
case 40: // arrow down
controlCam("down");
break;
case 13: //enter
saveImage();
break;
}
}
}

72
public/js/print.js Normal file
View File

@ -0,0 +1,72 @@
// CCTV Control
//
// Author: Florian Raemisch
// Email: olf@subsignal.org
//
// cctv-control is a webinterface to control and view
// mjpeg encoded videos by cctv cameras which are
// proxied by mjpegplexer. camera control is possible
// with arrow-keys, if mjpegplexer is configured
// properly.
//
// This Software is licensed under the GPL Version 3, 29 June 2007
var popen = '<p id="text">',
pclose = '</p>';
//create event which is triggered when DOM is ready
window.onDomReady = initReady;
//change from testbild to default cam when DOM is ready
window.onDomReady(initViewer);
function initReady(fn) {
if(document.addEventListener) {
document.addEventListener("DOMContentLoaded", fn, false);
}
}
function initViewer() {
//load contents from localStorage
var display = document.getElementById("camImage"),
meta = document.getElementById("metaContainer"),
text = document.getElementById("textContainer"),
formName = localStorage.getItem("formName"),
formDate = localStorage.getItem("formDate"),
formAnalysis1 = localStorage.getItem("formAnalysis1"),
formAnalysis2 = localStorage.getItem("formAnalysis2"),
formAnalysis3 = localStorage.getItem("formAnalysis3"),
camera = localStorage.getItem("cam");
stor = localStorage.getItem("snapShot");
//cameraname is cameraID + 1
camera = parseInt(camera) + 1;
//display loaded data
if (stor) {
display.setAttribute("src", stor);
meta.innerHTML += popen + formName + pclose;
meta.innerHTML += popen + formDate + pclose;
meta.innerHTML += popen + 'Kamera ' + camera + pclose;
text.innerHTML += popen + formAnalysis1 + pclose;
text.innerHTML += popen + formAnalysis2 + pclose;
text.innerHTML += popen + formAnalysis3 + pclose;
}
//commented out for debugging and layouting purposes
print();
//reset localStorage to default values
//localStorage.setItem("formName", "Untersucher");
//localStorage.setItem("formDate", "Datum");
//localStorage.setItem("formAnalysis1", "Dein Bericht 1");
//localStorage.setItem("formAnalysis2", "Dein Bericht 2");
//localStorage.setItem("formAnalysis3", "Dein Bericht 3"):
//commented out for debugging and layouting purposes
window.close();
}

24
public/print.html Normal file
View File

@ -0,0 +1,24 @@
<html>
<head>
<script src="js/print.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="css/print.css">
</head>
<body>
<div id="page">
<div id="imageContainer">
<img src="about:blank" id="camImage">
</div>
<div id="metaContainer">
</div>
<div id="textContainer">
</div>
</div>
</body>
</html>

View File

@ -11,16 +11,36 @@ var app = express();
var cameras = [
{
baseurl: 'http://admin:123456@192.168.1.10',
model: 'conceptronic'
baseurl: 'http://192.168.1.20:8080/cam/0',
model: 'mjpegplexer'
},
{
baseurl: 'http://admin:123456@192.168.1.11',
model: 'conceptronic'
baseurl: 'http://192.168.1.20:8080/cam/1',
model: 'mjpegplexer'
},
{
baseurl: 'http://admin:123456@192.168.1.12',
model: 'conceptronic'
baseurl: 'http://192.168.1.20:8080/cam/2',
model: 'mjpegplexer'
},
{
baseurl: 'http://192.168.1.20:8080/cam/3',
model: 'mjpegplexer'
},
{
baseurl: 'http://192.168.1.20:8080/cam/4',
model: 'mjpegplexer'
},
{
baseurl: 'http://192.168.1.20:8080/cam/5',
model: 'mjpegplexer'
},
{
baseurl: 'http://192.168.1.20:8080/cam/6',
model: 'mjpegplexer'
},
{
baseurl: 'http://192.168.1.20:8080/cam/7',
model: 'mjpegplexer'
}
];
@ -97,6 +117,8 @@ for (var camera_idx in cameras) {
app.use('/cam/' + camera_idx + '/browse',
express.static(__dirname + '/cam_public'));
}
app.use('/interface',
express.static(__dirname + '/public'));
app.get('/blackout', function(req, res) {
for (var camera_idx in cameras) {