cntk-video-tagging-tool
Version:
An electron app for building end to end Object Detection Models with CNTK from Sample Videos.
828 lines (811 loc) • 36.5 kB
HTML
<!--
* video-tagging control for video tagging
* Main file of the video-tagging module.
-->
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="video-taggingstyles.html">
<link rel="import" href="optional-tags.html">
<link rel="import" href="playback-control.html">
<dom-module id="video-tagging">
<template>
<style include="video-taggingstyles"></style>
<link rel="stylesheet" href="css/sliders.css" />
<link rel="stylesheet" href="../jquery-ui/themes/base/resizable.css">
<link rel="stylesheet" href="css/imgareaselect-animated.css" />
<link rel="stylesheet" href="../bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="assets/icons/style.css">
<div id="controlWrapper" class="controlWrapper">
<playback-control id="playSpeedControl" class="playSpeedControl"></playback-control>
<div id='videoWrapper' class="relativeDiv">
<div id = "regionLabelDiv" style="width:40px;height:20px;" class="regionLabel" >
<span id="regionLabelSpan" class="regionLabelSpan"></span>
<span id="closeRegionImage" on-click="deleteRegion" class="closeRegion clickableControl">X</span>
</div>
<canvas id="overlay" on-click='videoClicked' class="overlaystyle" >
Your browser does not support the HTML5 canvas location.
</canvas>
<video id='vid' class="videoStyle">
Your browser does not support the video location.
</video>
</div>
<div id="videoControls" class="videoControls">
<input id="seekBar" class="seek clickableControl" type="range" min='0' value="0" step="any" onkeydown="return false;" required />
<table id="videoControlsTable" class="videoControlsTable">
<tr>
<td class="videoControlCell simpleControl clickableControl">
<span id="stepbwd" title="prev" class="icon-backward2 taggingControls" on-click='stepBwdClicked'></span>
</td>
<td class="videoControlCell simpleControl clickableControl" style="display: none">
<span id="play-pause" title="play/pause" class="icon-play3 taggingControls" on-click='playPauseClicked' ></span>
</td>
<td class="videoControlCell simpleControl clickableControl">
<span id="stepfwd" title="next" class="icon-forward3 taggingControls" on-click='stepFwdClicked'></span>
</td>
<td class="videoControlCell simpleControl clickableControl">
<span id="nextuntagged" title="first untagged frame" class="icon-next2 taggingControls" on-click='nextUntaggedClicked'></span>
</td>
<td class="videoControlCell simpleControl clickableControl">
<span id="clearRegions" title="clear tags" class="glyphicon glyphicon-ban-circle taggingControls" on-click='clearRegions'></span>
</td>
<td class="videoControlCell frameNumber">
<span id='frameText' class="textElements" title="frame#"></span>
</td>
<td id="playSpeedCell" title="play speed" class="videoControlCell longControl clickableControl" style="display: none">
<span id="playbackSpan" class="textElements" on-click="playbackSpeedClicked">x 1.0</span>
</td>
<td nowrap="nowrap" class="videoControlCell simpleControl">
<span id="timeSpan" class="textElements"></span>
</td>
<td class ="videoControlCell simpleControl clickableControl" style="display: none">
<span id="mute" title="Mute" class="icon-volume-medium taggingControls" on-click='muteClicked'></span>
</td>
<td class="volumeControlCell longControl" style="display: none">
<input id="volumeSlider" class="volume clickableControl" type="range" min=0 max = 1 value=.5 step= .1 required />
</td>
</tr>
</table>
</div>
<div id="videoTagControls" class="videoTagControls">
<div class="optionalTags">
<div class="optionalTagsWrapper">
<optional-tags id="optionalTags"></optional-tags>
</div>
</div>
<div class="labelControls">
<span id="emptyFrame" title="empty frame" class="icon-share taggingControls controlOff" on-click='emptyFrameClicked' ></span>
<span id="lockTag" title="lock tags" class="icon-pushpin taggingControls lockTag controlOff" on-click='lockTagsClicked' ></span>
</div>
<div style="clear: both">
</div>
</div>
</div>
</template>
<script>
Polymer({
is: 'video-tagging',
properties: {
framerate: Number,
videoduration: Number,
videowidth: Number,
videoheight: Number,
regiontype: String,
multiregions: Number,
regionsize: Number,
inputtagsarray:Object,
inputframes: Object,
src: {
type: String,
value: '',
observer: 'videoSrcChanged'
}
},
// Element Lifecycle
ready: function() {
this.frames = {}; // Holds the data of the tagged frames, their regions and tags
this.seeking = false;//Flag for enabling control over the seek bar while the video is playing, see playingCallback function
this.selectedRegionId = 0;//Holds the current selected region number
this.lockTagsEnabled = false;
this.selectedTags = [];
this.uniqueTagId = 0;
this.videoStartTime = 0; //sometimes videos don't load at absoultue zero
this.canMove = true;
//Divs and spans
this.controlWrapper= this.$$('#controlWrapper');
this.videoWrapper= this.$$('#videoWrapper');
this.overlay = this.$$('#overlay');
this.video= this.$$('#vid');
this.optionalTags= this.$$('#optionalTags');
this.regionLabelDiv = this.$$('#regionLabelDiv');
this.regionLabelSpan = this.$$('#regionLabelSpan');
this.timeSpan = this.$$('#timeSpan');
this.frameText = this.$$("#frameText");
this.playbackSpan = this.$$("#playbackSpan");
// Buttons
this.playButton = this.$$('#play-pause');
this.stepfwd = this.$$("#stepfwd");
this.stepbwd = this.$$("#stepbwd");
this.lockTag = this.$$("#lockTag");
this.playSpeedControl = this.$$("#playSpeedControl");
this.playSpeedCell = this.$$("#playSpeedCell");
this.mute = this.$$('#mute');
this.emptyFrame = this.$$("#emptyFrame");
this.nextuntagged = this.$$("#nextuntagged");
// Sliders
this.seekBar = this.$$("#seekBar");
this.volumeSlider = this.$$("#volumeSlider");
//dynamic styles for sliders
this.volumeStyle = document.createElement('style');
Polymer.dom(this.root).appendChild(this.volumeStyle);
this.seekStyle = document.createElement('style');
Polymer.dom(this.root).appendChild(this.seekStyle);
this.playing = null;
this.ctx = this.overlay.getContext("2d");
this.aspect = 0;
this.snapWidth = 0;
},
/**
* Events registration and handling
*
* @method attached
* Events registration and handling
*/
attached: function() {
//Reset all variables to new src
this.video.addEventListener( "loadedmetadata", init);
var self = this;
function init() {
self.controlWrapper.style.display = "block";
//Init variables and controls
self.frames = self.inputframes? self.inputframes:{};
self.frameTime = 1/self.framerate;
self.enableAreaSelect(self);
self.optionalTags.createTagControls(self.inputtagsarray);
//Take the raw video aspect ratio
self.aspect = self.video.offsetWidth / self.video.offsetHeight;
self.snapToAspectRatio();
//Init sliders
self.volumeSlider.value = 0.5;
self.seekBar.max = self.video.duration;
self.playingCallback();
//Size canvas as video
self.overlay.width = self.video.offsetWidth;
self.overlay.height = self.video.offsetHeight;
//fix resize bug
$(window).resize( function(){
if (self.video.offsetWidth !== undefined){
self.snapToAspectRatio();
//get transform ratio
var transformWidth = self.video.offsetWidth/self.overlay.width;
var transformHeight = self.video.offsetHeight/self.overlay.height;
//resize the overlay
self.overlay.width = self.video.offsetWidth;
self.overlay.height = self.video.offsetHeight;
//resize the region boxes
$('.regionCanvas').each(function() {
this.style.left = (parseFloat(this.style.left) * transformWidth) + "px";
this.style.top = (parseFloat(this.style.top) * transformHeight) + "px";
this.style.width = (parseFloat(this.style.width) * transformWidth) + "px";
this.style.height = (parseFloat(this.style.height) * transformHeight) + "px";
});
//reposition selectedRegion Label
var selectedDiv = $('.regionCanvasSelected')[0];
self.positionRegionNameLabel(selectedDiv);
}
});
//bind keys (note this currently overrides any listener on the parent controls needs a more elegant way to work only when control is in focus)
window.addEventListener("keydown", (function(canMove) {
return function(e) {
if (!canMove) return false;
canMove = false;
setTimeout(function() { canMove = true; }, 100);
switch (e.keyCode) {
case 37: // left
self.stepBwdClicked();
break;
case 39: // right
self.stepFwdClicked();
break;
case 46: //delete
case 8: //backspace
if($('.regionCanvasSelected')[0]){
self.deleteRegion();
}
break;
default: return; // exit this handler for other keys
}
e.preventDefault(); // prevent the default action (scroll / move caret)
};
})(true), false);
}
this.video.onended = function(){
self.pauseState();
};
this.seekBar.addEventListener("mousedown", function() {
self.seeking = true;
});
this.seekBar.addEventListener("mouseup", function() {
self.seeking = false;
});
this.seekBar.addEventListener("change", function() {
self.seeking = false;
self.video.currentTime = Math.floor(self.seekBar.value/self.frameTime) * self.frameTime;//keep the frame in sync
self.playingCallback();
});
this.volumeSlider.addEventListener("change", function() {
self.video.volume = self.volumeSlider.value;
var perc = 100 * self.volumeSlider.value / self.volumeSlider.max;
self.volumeStyle.textContent = '.volume::-webkit-slider-runnable-track{background-size:'+ perc +'% 100%} ';
self.volumeStyle.textContent += '.volume::-moz-range-track{background-size:'+ perc +'% 100%} ';
});
this.addEventListener("playSpeedSelected", function(e) {
self.playback(e.detail.playbackValue, e.detail.playbackText);
});
this.addEventListener("ontagsubmitted", function(e) {
var arr = [];
arr.push(e.detail.tagid);
this.addTagsToRegion(arr);
this.emitRegionToHost();//Persist
});
this.addEventListener("ontagdeleted", function(e) {
var regionId = this.selectedRegionId -1;//Revert to zero-based array
var region = this.frames[this.getCurrentFrame()][regionId];
for(var index = 0;index < region.tags.length;index++){
if(region.tags[index] === e.detail.tagid){
region.tags.splice(index, 1);
break;
}
}
this.emitRegionToHost();
});
},
snapToAspectRatio: function()
{
// -5 accounts for rounding errors during rendering to ensure
// we never redraw larger than the parent container.
// We need to offset for the non-scaling parts of the video controls
this.snapWidth =
($('#video-tagging').parent().height() -
$('#videoControls').innerHeight() -
$('#videoTagControls').innerHeight() -
5) * this.aspect;
if(this.snapWidth > $('#video-tagging').parent().width())
$('#video-tagging').width($('#video-tagging').parent().width());
else
$('#video-tagging').width(this.snapWidth);
},
/**
* JQuery Area selector
* Based on JQuery plugin imgareaselect
* http://odyniec.net/projects/imgareaselect/usage.html
*
* Allows selection of a rectangle
*/
clearArea: function()
{
$('canvas#overlay').imgAreaSelect({
show:false,
hide:true
});
},
enableAreaSelect: function(self) {
$('canvas#overlay').imgAreaSelect({
disable: true,
show:false,
hide:true
});
if((this.regiontype.toLowerCase() === "square") || (this.regiontype.toLowerCase() === "rectangle")) {
$('canvas#overlay').imgAreaSelect({
disable: false, //enable/disable
handles: true, //grab handles when selecting the area
aspectRatio: '1:1',
maxWidth: self.overlay.offsetWidth,
maxHeight: self.overlay.offsetHeight,
minWidth:10,
fadeSpeed: 200,
square: (this.regiontype.toLowerCase() === "square"),
onSelectEnd: function(img, selection){
if (selection.width !== 0 && selection.height !== 0) {
self.areaSelected = true;
self.createRegion(selection.x1, selection.y1, selection.x2, selection.y2);
self.clearArea();
}
},
onSelectStart: function(img, selection){
self.cleanSelectedElements();
},
});
}
},
/**
* All functions related to regions management
*
* @method positionRegionNameLabel
* Positions the div with the region number next to the region div.
* @param {div} The region div
*/
//Regions management
updateRegionZIndices: function(){
$('.regionCanvas').sort(function (e1, e2) {
return ($(e1).width() * $(e1).height() <= $(e2).width() * $(e2).height());
}).each(function(idx,e) {
e.style.zIndex =(idx+200).toString();
});
},
positionRegionNameLabel: function(div) {
if (div){
this.regionLabelDiv.style.left = (div.offsetLeft + parseFloat(div.style.width, 10) - parseFloat(this.regionLabelDiv.style.width, 10)) + "px";
this.regionLabelDiv.style.top = (div.offsetTop - parseFloat(this.regionLabelDiv.style.height, 10)) + "px" ;
//Out of bounds top - move it below div
if(parseFloat(this.regionLabelDiv.style.top) < 0) {
this.regionLabelDiv.style.top = (div.offsetTop + parseFloat(div.style.height, 10)) + "px" ;
}
this.regionLabelSpan.innerHTML = div.id;
this.regionLabelDiv.style.display = "block";
}
},
createRegion: function(x1, y1, x2, y2) {
var region = this.addRegion(x1, y1, x2, y2);//Add to in-memory collection
this.addDivToRegion(x1, y1, x2 , y2, region.name);//add frame
this.updateRegionZIndices();//update region z indecies
this.regionSelected(region.name);//Select it by default
//if there is only one tag enable it by default
if ($(`#${this.optionalTags.id}`).find('.tagButtons').size() == 1){
$(`#${this.optionalTags.id}`).find('.tagButtons')[0].click();
}
if(this.lockTagsEnabled) {
//Get all selected tags and add them to current region automatically
//this.selectedTags was populated in this.lockTagsClicked
var arr = [];
for (var i=0; i<this.selectedTags.length;i++) {
this.optionalTags.setSelected(this.selectedTags[i]);
arr.push(this.selectedTags[i].id);
}
this.addTagsToRegion(arr);
var self = this;
//Auto step functionality - Goes to next frame automatically
if(this.multiregions === "0") {
setTimeout(function(){ self.stepFwdClicked(); }, 500);
}
}
this.emitRegionToHost();//Persist
},
addTagsToRegion: function(selectedTagsArray) {
var regionId = this.selectedRegionId -1;//Revert to zero-based array
var region = this.frames[this.getCurrentFrame()][regionId];
for (var i = 0; i < selectedTagsArray.length; i++) {
region.tags.push(selectedTagsArray[i]);
}
},
nextUntaggedClicked :function() {
var frameIndex = this.getCurrentFrame();
var frames = this.video.duration * this.framerate;
var framesForward = 1;
for (var i=frameIndex;i<frames;i++) {
//The next frame is a first occurence of a frame with no regions
if(!this.frames[i + 1]) {
if(!this.video.paused) {
this.playPauseClicked();//Will pause the video
}
this.video.currentTime += (this.frameTime * framesForward);
this.playingCallback();
break;
}
framesForward++;
}
},
lockTagsClicked: function() {
var selTags = this.optionalTags.getSelectedTags();
//There has to be selected label/s
if (!this.lockTagsEnabled && selTags.length > 0) {
this.lockTagsEnabled = true;
this.selectedTags = selTags;
if(this.multiregions === "0"){this.stepFwdClicked();}
}
else {
this.lockTagsEnabled = false;
this.selectedTags = [];
this.optionalTags.resetSelected();
}
this.lockTag.classList.toggle("controlOn", this.lockTagsEnabled);
this.lockTag.classList.toggle("controlOff", !this.lockTagsEnabled);
},
regionSelected: function(divId) {
this.cleanSelectedElements();
var div = document.getElementById(divId);
div.classList.add("regionCanvasSelected");
this.selectedRegionId = div.id;
this.positionRegionNameLabel(div);
//Tags - display the tags of the region and enable editing
this.optionalTags.toggleEnableButtons(true);
this.optionalTags.resetSelected();
var regions = this.frames[this.getCurrentFrame()];
if(regions) {
var tags = this.frames[this.getCurrentFrame()][this.selectedRegionId - 1].tags;
this.optionalTags.displaySelectedTags(tags);
}
function updateRegionFromDiv(div){
var region = regions[(divId-1).toString()];
region.width = $('#vid').width();
region.height = $('#vid').height();
region.x1 = parseInt(div.style.left);
region.y1 = parseInt(div.style.top);
region.x2 = region.x1 + parseInt(div.style.width);
region.y2 = region.y1 + parseInt(div.style.height);
}
//make region draggable
$('#'+divId).draggable({
stop: function(event, ui) {
updateRegionFromDiv(this);
},
containment: $("#overlay")[0]
});
//make region resizable
$('#'+divId).resizable({
stop: function(event, ui) {
updateRegionFromDiv(this);
$('.regionCanvas').sort(function (e1, e2) {
return ($(e1).width() * $(e1).height() <= $(e2).width() * $(e2).height());
}).each(function(idx,e) {
e.style.zIndex =(idx+200).toString();
});
},
containment: $("#overlay")[0],
handles: 'all'
});
//raise regionSelectedEvent
$('#video-tagging').trigger('canvasRegionSelected',[divId]);
},
deleteRegion: function(e) {
var regions = this.frames[this.getCurrentFrame()];
var deletedRegion = regions[this.selectedRegionId - 1];
regions.splice(this.selectedRegionId - 1, 1);
//Shift array left to cover removed item - Rename all regions which are higher than the deleted one,
for (var i=0;i<regions.length;i++) {
var id = Number(regions[i].name);
if(id > this.selectedRegionId){
regions[i].name = id - 1;
}
}
this.showAllRegions();
this.emitRegionToHost();
},
clearRegions: function(e) {
this.frames[this.getCurrentFrame()]=[];
this.showAllRegions();
this.emitRegionToHost();
},
/**
* @method emitRegionToHost
* Fires an event to send the array of regions.
* The host html page can listen to this event.
*/
emitRegionToHost: function() {
var frameIndex = this.getCurrentFrame();
this.fire('onregionchanged', {frame: {frameIndex:frameIndex, regions:this.frames[frameIndex]}});
},
/**
* @method addDivToRegion
* Adds a transparent div to the region, the size of the region.
* Used for click events, hover, etc..
* @params {x1, y1, x2, y2} region coordinates.
* @param {regionId} The region id, which is the region index + 1.
*/
addDivToRegion: function(x1, y1, x2, y2, regionId) {
var div = document.createElement("div");
div.id = regionId;
div.classList.add("regionCanvas");
if(this.regiontype.toLowerCase() === "point") {
div.style.width = this.regionsize - 1 + "px";//Compensate for 1px border
div.style.height = this.regionsize - 1 + "px";
div.style.top = y1 - this.regionsize/2 + "px";
div.style.left = x1 - this.regionsize/2 + "px";
div.classList.add("regionPoint");
}
if((this.regiontype.toLowerCase() === "square") || (this.regiontype.toLowerCase() === "rectangle")) {
div.style.width = x2 - x1 + "px";
div.style.height = y2 - y1 + "px";
div.style.top = y1 + "px";
div.style.left = x1 + "px";
}
var self = this;
$( div )
.click(function(e) {
self.regionSelected(div.id);
});
$( div )
.mouseenter(function(e) {
self.positionRegionNameLabel(this);
})
.mouseleave(function() {
self.regionLabelDiv.style.display = "none";
if(self.selectedRegionId === div.id) {
self.regionLabelDiv.style.display = "block";
}
});
Polymer.dom(this.$.videoWrapper).appendChild(div);
},
/**
* @method cleanSelectedElements
* Removes all borders of regions and disables label buttons
*/
cleanSelectedElements: function() {
//Remove selected style
var regionCanvases = Polymer.dom(this.root).querySelectorAll('.regionCanvas');
for (var i=0;i<regionCanvases.length;i++) {
regionCanvases[i].classList.remove('regionCanvasSelected');
}
//reset
this.selectedRegionId = 0;
this.optionalTags.toggleEnableButtons(false);
this.optionalTags.resetSelected();
},
showAllRegions: function() {
this.clearFrameElements();//Clear canvas and tags
this.cleanSelectedElements();//Clear selected regions
var frameIndex = this.getCurrentFrame();
var regions = this.frames[frameIndex];
if(regions && regions.length > 0) {
//Draw all regions for this frame
for (var i=0; i<regions.length;i++) {
var region = regions[i];
//Frame was tagged as empty?
if(Object.keys(region).length === 0) {
this.indicateEmptyFrame(true);
continue;
}
//Calculate x, y relative to current width and height
var widthRatio = $('#vid').width() / region.width;
var heightRatio = $('#vid').height() / region.height ;
var x1 = (region.x1 * widthRatio);
var y1 = (region.y1 * heightRatio);
var x2 = (region.x2 * widthRatio);
var y2 = (region.y2 * heightRatio);
x1 = (region.x1 * widthRatio);
y1 = (region.y1 * heightRatio);
x2 = (region.x2 * widthRatio);
y2 = (region.y2 * heightRatio);
this.addDivToRegion( x1, y1, x2, y2, region.name);
//Only 1 region - select it and show tags
if(regions.length === 1) {
this.regionSelected(region.name);
}
this.updateRegionZIndices();
}
}
},
/**
* @method addRegion
* Adds a region to the array of regions per current frame.
* @params {x1, y1, x2, y2} region coordinates.
*/
addRegion: function(x1, y1, x2, y2) {
this.resetEmptyFrame();//Clear empty frame logic
var region = {};
region.x1 = x1;
region.y1 = y1;
region.x2 = x2;
region.y2 = y2;
region.id = this.uniqueTagId++;
region.width = $('#vid').width();
region.height = $('#vid').height();
region.type = this.regiontype;
region.tags = [];
var frameIndex = this.getCurrentFrame();
var regions = this.frames[frameIndex];
//The array is populated and can contain multiple regions
if(this.multiregions == 1 && regions) {
region.name = regions.length + 1;
this.frames[frameIndex].push(region);
}
else {//Only one region allowed
this.clearFrameElements();
region.name = 1;
this.frames[frameIndex] = [];
this.frames[frameIndex].push(region);
}
return region;
},
/**
* All functions related to frames management
*
* @method clearFrameElements
* Clears all drawings and divs from the video area and resets tag controls.
*/
clearFrameElements: function() {
//Clear divs
$(this.videoWrapper).children(".regionCanvas").remove();
//reset tag buttons to not selected
this.optionalTags.resetSelected();
//hide region number
self.regionLabelDiv.style.display = "none";
//Clears the empty frame icon
this.indicateEmptyFrame(false);
},
getCurrentFrame: function() {
//if (this.videoStartTime == 0) return Math.ceil((this.video.currentTime - this.videoStartTime) * this.framerate) + 1 ;
return this.video.currentTime === 0 ? 1 : Math.ceil((this.video.currentTime - this.videoStartTime) * this.framerate) + 1 ;
},
indicateEmptyFrame : function(selected) {
if(selected) {
this.emptyFrame.classList.remove("controlOff");
this.emptyFrame.classList.add("controlOn");
}
else {
this.emptyFrame.classList.remove("controlOn");
this.emptyFrame.classList.add("controlOff");
}
},
/**
* @method emptyFrameClicked
* Creates an empty region array for the current frame, meaning that the frame is not tagged, but has been reviewed.
*/
emptyFrameClicked: function() {
var frameIndex = this.getCurrentFrame();
var regions = this.frames[frameIndex];
if(!regions || regions.length === 0) {
this.frames[frameIndex] = [{}];
this.indicateEmptyFrame(true);
this.emitRegionToHost();
if(this.lockTagsEnabled){
this.stepFwdClicked();
}
}
},
/**
* @method resetEmptyFrame
* If the frame has been marked as empty - reset that.
*/
resetEmptyFrame: function() {
var regions = this.frames[this.getCurrentFrame()];
//If there is an empty region
if(regions && regions.length === 1 && Object.keys(regions[0]).length === 0) {
//reset all for this frame
this.frames[this.getCurrentFrame()] = [];
this.clearFrameElements();
}
},
/**
* Video management
*
*/
muteClicked: function() {
this.mute.classList.toggle("icon-volume-mute2", !this.video.muted);
this.mute.classList.toggle("icon-volume-medium", this.video.muted);
this.video.muted = !this.video.muted;
},
/**
* Handler for setting the video play speed
*/
playback: function(val, text) {
if(val !== null){
this.video.playbackRate = val;
this.playbackSpan.innerHTML = text;
}
this.playSpeedControl.style.display = "none";
},
/**
* Shows the play speed control
*/
playbackSpeedClicked: function() {
var offset = $('#playSpeedCell').offset();
var top = offset.top - $('#playSpeedControl').height();
var left = offset.left + $('#playSpeedCell').width() / 4;
$('#playSpeedControl').css({'left': left, 'top': top});
this.playSpeedControl.style.display = this.playSpeedControl.style.display === "block"?"none":"block";
},
playPauseClicked: function() {
this.video.paused ? this.playState():this.pauseState();
},
getUnlabeledRegionTags: function(regionId){
var regions = this.frames[regionId];
var unlabledTags = [];
if (regions !== undefined){
unlabledTags = regions.map(function(region,index){
if (!region.tags.length > 0) return index + 1;
}).filter(Number.isInteger);
}
return unlabledTags;
},
checkRegionLabels: function(){
var unlabledTags = this.getUnlabeledRegionTags(this.getCurrentFrame());
if (unlabledTags.length > 0){
alert(`Cannot move to the next frame until all tags are labeled. Please label the following tags [${unlabledTags}] on the displayed frame.`);
return false;
}
return true;
},
stepFwdClicked: function(fireEvents = true) {
if (!this.canMove) return;
if(!this.video.paused) this.pauseState();
if ((this.video.currentTime + this.frameTime) > this.video.duration ){
this.video.currentTime = this.video.duration;
return;
}
if (this.checkRegionLabels()){
//raise before next frame
if(fireEvents)$('#video-tagging').trigger('stepFwdClicked-BeforeStep');
//if there are no unlabled tags move to next frame
this.video.currentTime += this.frameTime;
this.playingCallback();
//raise after next frame
if(fireEvents)$('#video-tagging').trigger('stepFwdClicked-AfterStep');
}
},
stepBwdClicked: function() {
if (!this.canMove) return;
if (this.video.currentTime > 0) {
if(!this.video.paused) {
this.pauseState();
}
if (this.checkRegionLabels()){
this.video.currentTime -= this.frameTime;
this.playingCallback();
}
}
},
videoSrcChanged: function(newValue, oldValue) {
if(this.video){
this.video.src = newValue;
}
},
videoClicked: function(e) {
this.cleanSelectedElements();
if(!((this.regiontype.toLowerCase() === "square") || (this.regiontype.toLowerCase() === "rectangle")))
{
var rect = this.overlay.getBoundingClientRect();
var x1 = (e.clientX-rect.left)/(rect.right-rect.left)*this.overlay.width;
var y1 = (e.clientY-rect.top)/(rect.bottom-rect.top)*this.overlay.height;
this.createRegion(x1, y1, null, null);
}
},
playState: function() {
this.video.play();
this.playButton.classList.toggle("icon-pause2", !this.video.paused);
//Reset lock tags to off
this.lockTagsEnabled = true;
this.lockTagsClicked();
this.optionalTags.toggleEnableButtons(false);
$('canvas#overlay').imgAreaSelect({disable: true});//disable canvas
var self = this;
this.playing = setInterval(function() {
self.playingCallback();
}, 10);
},
pauseState: function() {
this.video.pause();
clearInterval(this.playing);
this.video.currentTime = Math.floor(this.video.currentTime/this.frameTime) * this.frameTime;//keep the frame in sync
this.playingCallback();
this.playButton.classList.toggle("icon-pause2", !this.video.paused);
this.optionalTags.toggleEnableButtons(false);
$('canvas#overlay').imgAreaSelect({disable: false});//enable canvas
},
playingCallback: function() {
this.frameText.innerHTML = this.getCurrentFrame();
this.displayVideoTime();
if(!this.seeking){
this.updateSeekBar();
}
this.showAllRegions();
},
displayVideoTime: function() {
var currentTime = Math.round(this.video.currentTime);
var remainingtTime = Math.round(this.video.duration - this.video.currentTime);
//Format using moment.js
currentTime = moment().startOf('day').seconds(currentTime).format('HH:mm:ss');
remainingtTime = moment().startOf('day').seconds(remainingtTime).format('HH:mm:ss');
this.timeSpan.innerHTML = currentTime + " / " + remainingtTime;
},
updateSeekBar: function() {
this.seekBar.value = this.video.currentTime;
var perc = 100 * this.seekBar.value / this.seekBar.max;
this.seekStyle.textContent = '.seek::-webkit-slider-runnable-track{background-size:'+perc+'% 100%}';
this.seekStyle.textContent += '.seek::-moz-range-track{background-size:'+perc+'% 100%}';
},
});
</script>
<script src="../jquery/dist/jquery.min.js"></script>
<script src="../moment/moment.js"></script>
<script src="js/jquery.imgareaselect.js"></script>
<script src="../jquery-ui/jquery-ui.min.js"></script>
</dom-module>