Source: video.js

/** @file File containing the video class 

* Video is a mirror class of image.js. Most of the functions are
* similar and could be joined
*/

/**
 * Creates a video object
 * @constructor
 * @param {string} id - The id of the dom element containing the video
*/
function video(id) {
    
    // *******************************************
    // Private variables:
    // *******************************************
    this.page_in_use = 0; // Describes if we already see a video.
    this.id = id;
    this.dir_name = null;
    this.im = document.getElementById("im");
    this.dir_name;
    this.video_name = null;
    this.width_orig;
    this.height_orig;
    this.width_curr;  //current width and height of the image itself
    this.height_curr;
    this.im_ratio; // Ratio of (displayed image dims) / (orig image dims)
    this.browser_im_ratio; // Initial im_ratio; this should not get changed!!
    this.curr_frame_width;  // Current width of main_media.
    this.curr_frame_height; // Current height of main_media.
    
    

    
    // *******************************************
    // Public methods:
    // *******************************************
    
    /** Fetches a new video based on the URL string. Creates a div container to store the video */

    this.GetNewVideo = function(onload_helper) {

        var videodiv = '<div id="'+id+'" videosrc="" videoautoplay="true" style="vertical-align:bottom;z-index:-4;"></div>';
        $('#main_section').append(videodiv);
        $('#main_media').detach().appendTo('#'+id);
        document.getElementById('loading').style.display = '';
        if(IsMicrosoft()) this.im.style.visibility = 'hidden';
        else this.im.style.display = '';
        wait_for_input = 0;
        edit_popup_open = 0;
        this.SetImageDimensions();
        console.time('Load LabelMe XML file');
        oVP = new JSVideo();
        console.time('Load video');
        oVP.loadChunk(1, 1, true, false);
    };
    
    /** Returns the ratio of the available width/height to the original
     * width/height. For now, it is always 1 */
    this.GetImRatio = function() {
        return this.im_ratio;
    };
    
    /** Returns self object in order to be compliant with the functions from file_info*/
    this.GetFileInfo = function() {
        return this;
    };
    
    
    /** Sets the dimensions of the image based on browser setup. For now it sets 640x480 */
    this.SetImageDimensions = function() {
        this.im_ratio = 1.;
        this.width_curr = 640;
        this.height_curr = 480;
        this.width_orig = 640;
        this.height_orig = 480;
        
        
        
        $("#myCanvas_bg").width(this.width_curr).height(this.height_curr);
        $("#select_canvas").width(this.width_curr).height(this.height_curr);
        $("#draw_canvas").width(this.width_curr).height(this.height_curr);
        $("#query_canvas").width(this.width_curr).height(this.height_curr);
        
        $("#main_media").width(this.width_curr).height(this.height_curr);
        this.curr_frame_width = this.width_curr;
        this.curr_frame_height = this.height_curr;
        document.getElementById('loading').style.visibility = 'hidden';
        document.getElementById('main_media').style.visibility = 'visible';

        if(IsMicrosoft()) {
            this.im.style.visibility = '';
            document.getElementById('main_media').style.overflow = 'visible';
            this.ScaleFrame();
        }
        else this.im.style.display = '';
    };
    
    
    /** If (x,y) is not in view, then scroll it into view.  Return adjusted
     * (x,y) point that takes into account the slide offset.
     * @param {int} x
     * @param {int} y
     * @returns {intarray}
    */
    this.SlideWindow = function (x,y) {
        var pt = Array(2);
        if(!this.IsPointVisible(x,y)) {
            document.getElementById('main_media').scrollLeft = x-100;
            document.getElementById('main_media').scrollTop = y-100;
        }
        pt[0] = x-$("#main_media").scrollLeft();
        pt[1] = y-$("#main_media").scrollTop();
        return pt;
    };
    
    /** Turn off image scrollbars if zoomed in. */    
    this.ScrollbarsOff = function () {
        if(!this.IsFitImage()) {
            document.getElementById('main_media').style.overflow = 'hidden';
        }
    };
    
    /** Turn on image scrollbars if zoomed in. */

    this.ScrollbarsOn = function () {
        if(!this.IsFitImage()) {
            document.getElementById('main_media').style.overflow = 'auto';
        }
    };
    
    /** Not working now */
    this.Zoom = function(amt) {
        // if a new polygon is being added while the user press the zoom button then do nothing.
        if(wait_for_input) return;
        
        // if an old polygon is being edited while the user press the zoom button then close the polygon and zoom.
        if(edit_popup_open) StopEditEvent();
        
        if(amt=='fitted') {
                this.im_ratio = this.browser_im_ratio;
        } else {
                this.im_ratio = this.im_ratio * amt;
        }
        
        // if the scale factor is bellow the original scale, then do nothing (do not make the image too small)
        if(this.im_ratio < this.browser_im_ratio) {this.im_ratio=this.browser_im_ratio; return;}
        
        // New width and height of the rescaled picture
        this.width_curr = Math.round(this.im_ratio*this.width_orig);
        this.height_curr = Math.round(this.im_ratio*this.height_orig);
        
        // Scale and scroll the image so that the center stays in the center of the visible area
        this.ScaleFrame(amt);
        
	// Remove polygon from draw canvas:
	var anno = null;
	if(draw_anno) {
	  draw_anno.DeletePolygon();
	  anno = draw_anno;
	  draw_anno = null;
        }

        // set the size of the image (this.im is the image object)
        this.im.width = this.width_curr;
        this.im.height = this.height_curr;
        // set the size of all the canvases
        $("#myCanvas_bg").width(this.width_curr).height(this.height_curr);
        $("#select_canvas").width(this.width_curr).height(this.height_curr);
        $("#draw_canvas").width(this.width_curr).height(this.height_curr);
        $("#query_canvas").width(this.width_curr).height(this.height_curr);
        
        // Redraw polygons.
	main_canvas.RenderAnnotations();

	if(anno) {
	  // Draw polyline:
	  draw_anno = anno;
	  draw_anno.SetDivAttach('draw_canvas');
	  draw_anno.DrawPolyLine();
	}

	/*************************************************************/
	/*************************************************************/
	// Scribble: 
	if (drawing_mode == 1){
	  scribble_canvas.redraw();
	  scribble_canvas.drawMask();
        }
	/*************************************************************/
	/*************************************************************/
    };
    
    
    
    // *******************************************
    // Private methods:
    // *******************************************
    
    /** Tells the picture to take up the available space in the browser, if it needs it. 6.29.06*/
    
    this.ScaleFrame = function(amt) {
        // Look at the available browser (height,width) and the image (height,width),
        // and use the smaller of the two for the main_media (height,width).
        // also center the image so that after rescaling, the center pixels visible stays at the same location
        //var avail_height = this.GetAvailHeight();
        this.curr_frame_height = Math.min(this.GetAvailHeight(), this.height_curr);
        
        //var avail_width = this.GetAvailWidth();
        this.curr_frame_width = Math.min(this.GetAvailWidth(), this.width_curr);
        
        // also center the image so that after rescaling, the center pixels visible stays at the same location
        cx = $("#main_media").scrollLeft()+this.curr_frame_width/2.0; // current center
        cy = $("#main_media").scrollTop()+this.curr_frame_height/2.0;
        Dx = Math.max(0, $("#main_media").scrollLeft()+(amt-1.0)*cx); // displacement needed
        Dy = Math.max(0, $("#main_media").scrollTop()+(amt-1.0)*cy);
        
        // set the width and height and scrolls
        $("#main_media").scrollLeft(Dx).scrollTop(Dy);
        $("#main_media").width(this.curr_frame_width).height(this.curr_frame_height);
        
    };
    
    
    /** Retrieves and sets the original image's dimensions (width/height).
     * @param {image} im
    */
    this.SetOrigImDims = function (im) {
        this.width_orig = im.width;
        this.height_orig = im.height;
        return;
    };
    
    /** gets available width (6.14.06) */
    this.GetAvailWidth = function() {
        return $(window).width() - $("#main_media").offset().left -10 - 200;
        // we could include information about the size of the object box using $("#anno_list").offset().left
    };
    
    /** gets available height (6.14.06) */
    this.GetAvailHeight = function() {
        var m = main_media.GetFileInfo().GetMode();
        if(m=='mt') {
            return $(window).height() - $("#main_media").offset().top -75;
        }
        return $(window).height() - $("#main_media").offset().top -10;
    };
    
    
    
    /** Returns true if the image is zoomed to the original (fitted) resolution. */
    this.IsFitImage = function () {
        return (this.im_ratio < 0.01+this.browser_im_ratio);
    };
    
    /** Returns true if (x,y) is viewable. */
    this.IsPointVisible = function (x,y) {        
        var scrollLeft = $("#main_media").scrollLeft();
        var scrollTop = $("#main_media").scrollTop();
        
        if(((x*this.im_ratio < scrollLeft) ||
            (x*this.im_ratio - scrollLeft > this.curr_frame_width - 160)) || 
           ((y*this.im_ratio < scrollTop) || 
            (y*this.im_ratio - scrollTop > this.curr_frame_height))) 
            return false;  //the 160 is about the width of the right-side div
        return true;
    };

    /** Parses url again, knowing that is a video. The function is almost a replica of that in file_info */
    this.ParseURL = function () {
        var labelme_url = document.URL;
        var idx = labelme_url.indexOf('?');
        if((idx != -1) && (this.page_in_use == 0)) {
            this.page_in_use = 1;
            var par_str = labelme_url.substring(idx+1,labelme_url.length);
            var isMT = false; // In MT mode?
            var default_view_ObjList = false;
            do {
                idx = par_str.indexOf('&');
                var par_tag;
                if(idx == -1) par_tag = par_str;
                else par_tag = par_str.substring(0,idx);
                var par_field = this.GetURLField(par_tag);
                var par_value = this.GetURLValue(par_tag);
                if(par_field=='mode'){
                    this.mode = par_value;
                    if(this.mode=='im' || this.mode=='mt') view_ObjList = false;
                }
                if(par_field=='username') {
                    username = par_value;
                }
                
                if(par_field=='folder') {
                    this.dir_name = par_value;
                    console.log(par_value);
                }
                if(par_field=='videoname') {
                    this.video_name = par_value;
                    
                }
                
                if((par_field=='scribble')&&(par_value=='true')) {
                    scribble_mode = true;
                }
                if((par_field=='video')&&(par_value=='true')) {
                    video_mode = true;
                }
                par_str = par_str.substring(idx+1,par_str.length);
            } while(idx != -1);

            
            
            if((this.mode=='i') || (this.mode=='c') || (this.mode=='f')) {
                document.getElementById('body').style.visibility = 'visible';
            }
            
            else {
                this.mode = 'i';
                document.getElementById('body').style.visibility = 'visible';
            }
            
            if(!view_ObjList) {
                var p = document.getElementById('anno_anchor');
                p.parentNode.removeChild(p);
            }
            
        }
        
        return 1;
    };


    /** Gets mode */
    this.GetDirName = function () {
        return this.dir_name;
    };

    /** Gets image name */
    this.GetImName = function () {
        return this.video_name;
    };

    /** Gets image path */
    this.GetImagePath = function () {
        if((this.mode=='i') || (this.mode=='c') || (this.mode=='f') || (this.mode=='im') || (this.mode=='mt')) return 'VLMVideos/' + this.dir_name + '/' + this.video_name;
    };

    /** Gets full image name */
    this.GetFullName = function () {
        if((this.mode=='i') || (this.mode=='c') || (this.mode=='f') || (this.mode=='im') || (this.mode=='mt')) return this.dir_name + '/' + this.video_name;
    };

    /** Gets template path */
    this.GetTemplatePath = function () {
        if(!this.dir_name) return 'annotationCache/XMLTemplates/labelme.xml';
        return 'annotationCache/XMLTemplates/' + this.dir_name + '.xml';
    };

    /** String is assumed to have field=value form.  Parses string to
    return the field. */
    this.GetURLField = function (str) {
        var idx = str.indexOf('=');
        return str.substring(0,idx);
    };
    
    /** String is assumed to have field=value form.  Parses string to
    return the value. */
    this.GetURLValue = function (str) {
        var idx = str.indexOf('=');
        return str.substring(idx+1,str.length);
    };
    /** Computes a set of points representing a linearly interpolated polygon.
     * @param {array} xinit - xcoordinates of the polygon at tinit
     * @param {array} yinit - ycoordinates of the polygon at tinit
     * @param {array} xend - xcoordinates of the polygon at tend
     * @param {array} yend - ycoordinates of the polygon at tend
     * @param {int} tinit - first ground truth frame number
     * @param {int} tend - last ground truth frame number
     * @param {int} tcurrent - frame that has to be interpolated
    */
    this.GetInterpolatedPoints = function (xinit, yinit, xend, yend, tinit, tend, tcurrent){
        Xresp = Array(xinit.length);
        Yresp = Array(xinit.length);
        for (var i = 0; i <Xresp.length; i++){
            alfa = (tend - tcurrent)/(tend-tinit);
            Xresp[i] = alfa*xinit[i] + (1-alfa)*xend[i];
            Yresp[i] = alfa*yinit[i] + (1-alfa)*yend[i];
        }
        return [Xresp, Yresp];
    }

    /** Update the position of an annotation. Modifies the xml file information about a given annotation.
     *  @param {annotation} anno - annotation containing the info about the polygon
    */
    this.UpdateObjectPosition = function (anno){
      console.log(LM_xml);
      var obj_ndx = anno.anno_id;
      var curr_obj = $(LM_xml).children("annotation").children("object").eq(obj_ndx);
      var framestamps = (curr_obj.children("polygon").children("t").text()).split(',');
      var userlabeledframes = (curr_obj.children("polygon").children("userlabeled").text()).split(',');
      var pts_x = (curr_obj.children("polygon").children("x").text()).split(';');
      var pts_y = (curr_obj.children("polygon").children("y").text()).split(';');

      for(var ti=0; ti<framestamps.length; ti++) { 
        framestamps[ti] = parseInt(framestamps[ti], 10); 
      }
      for (var ti = 0; ti < userlabeledframes.length; ti++){
        userlabeledframes[ti] = parseInt(userlabeledframes[ti],10);
      }
      while (framestamps[0] > oVP.getcurrentFrame()){
        framestamps.unshift(framestamps[0]-1);
        pts_x.unshift(pts_x[0]);
        pts_y.unshift(pts_y[0]);
      }
      var ti = 0;
      while (ti < userlabeledframes.length && userlabeledframes[ti] <= oVP.getcurrentFrame()) ti++;
      var ti2 = 0;
      while (ti2 < userlabeledframes.length && userlabeledframes[ti2] < oVP.getcurrentFrame()) ti2++;
      ti2--;//

      var framenext = framestamps.length;
      var frameprior = -1;
      if (ti2 >= 0) frameprior = framestamps.indexOf(userlabeledframes[ti2]);
      if (ti < userlabeledframes.length) framenext = framestamps.indexOf(userlabeledframes[ti]);
      var objectind = framestamps.indexOf(oVP.getcurrentFrame());
      // backward interpolation
      for (var i = frameprior+1; i < objectind; i++){
        var coords = [anno.pts_x, anno.pts_y];
        if (frameprior > -1){
            var Xref = pts_x[frameprior].split(',');
            var Yref = pts_y[frameprior].split(',');
            coords = this.GetInterpolatedPoints(Xref, Yref, anno.pts_x, anno.pts_y, framestamps[frameprior], framestamps[objectind], framestamps[i]);
        }
        
        pts_x[i] = coords[0].join();
        pts_y[i] = coords[1].join();
      }
      pts_y[objectind] = anno.pts_y;
      pts_x[objectind] = anno.pts_x;
      // forward interpolation
      for (var i = objectind+1; i < framenext; i++){
        var coords = [anno.pts_x, anno.pts_y];
        if (framenext < framestamps.length){
            var Xref = pts_x[framenext].split(',');
            var Yref = pts_y[framenext].split(',');
            coords = this.GetInterpolatedPoints(anno.pts_x, anno.pts_y, Xref, Yref, framestamps[objectind], framestamps[framenext], framestamps[i]);
        }
        pts_x[i] = coords[0].join();
        pts_y[i] = coords[1].join();
      }
      userlabeledframes.push(oVP.getcurrentFrame());
      jQuery.unique(userlabeledframes);
      userlabeledframes.sort(function(a, b){return a-b});
      new_x_str = pts_x.join(';');
      new_y_str = pts_y.join(';');
      curr_obj.children("polygon").children("t").text(framestamps.join(','));
      curr_obj.children("polygon").children("x").text(new_x_str);
      curr_obj.children("polygon").children("y").text(new_y_str);   
      curr_obj.children("polygon").children("userlabeled").text(userlabeledframes.join());  
        
    }

    /** Submits an edited object, stored in select_anno */
    this.SubmitEditObject = function (){
        submission_edited = 1;
        var anno = select_anno;
      
      // object name
      old_name = LMgetObjectField(LM_xml,anno.anno_id,'name');
      if(document.getElementById('objEnter')) new_name = RemoveSpecialChars(document.getElementById('objEnter').value);
      else new_name = RemoveSpecialChars(adjust_objEnter);
      
      var re = /[a-zA-Z0-9]/;
      if(!re.test(new_name)) {
        alert('Please enter an object name');
        return;
      }
      
      if (use_attributes) {
        // occlusion field
        if (document.getElementById('occluded')) new_occluded = RemoveSpecialChars(document.getElementById('occluded').value);
        else new_occluded = RemoveSpecialChars(adjust_occluded);
        
        // attributes field
        if(document.getElementById('attributes')) new_attributes = RemoveSpecialChars(document.getElementById('attributes').value);
        else new_attributes = RemoveSpecialChars(adjust_attributes);
      }
      
      StopEditEvent();
      
      // Object index:
      var obj_ndx = anno.anno_id;
      
      // Pointer to object:
      var curr_obj = $(LM_xml).children("annotation").children("object").eq(obj_ndx);
      
      // Set fields:
      curr_obj.children("name").text(new_name);
      if(curr_obj.children("automatic").length > 0) curr_obj.children("automatic").text("0");
      
      // Insert attributes (and create field if it is not there):
      if(curr_obj.children("attributes").length>0) curr_obj.children("attributes").text(new_attributes);
      else curr_obj.append("<attributes>" + new_attributes + "</attributes>");
        
      if(curr_obj.children("occluded").length>0) curr_obj.children("occluded").text(new_occluded);
      else curr_obj.append("<occluded>" + new_occluded + "</occluded>");
      this.UpdateObjectPosition(anno);  
      oVP.DisplayFrame(oVP.getcurrentFrame());    
      
    }

    /** Submits an object for the first time. It is the equivalent of SubmitQuery for video */
    this.SubmitObject = function (){
        var nn;
        var anno;
        if (use_attributes) {
            // get attributes (is the field exists)
            if(document.getElementById('attributes')) new_attributes = RemoveSpecialChars(document.getElementById('attributes').value);
            else new_attributes = "";
            
            // get occlusion field (is the field exists)
            if (document.getElementById('occluded')) new_occluded = RemoveSpecialChars(document.getElementById('occluded').value);
            else new_occluded = "";
        }
        if((object_choices!='...') && (object_choices.length==1)) {
            nn = RemoveSpecialChars(object_choices[0]);
            active_canvas = REST_CANVAS;
            // Move draw canvas to the back:
            document.getElementById('draw_canvas').style.zIndex = -2;
            document.getElementById('draw_canvas_div').style.zIndex = -2;
            var anno = null;
            if(draw_anno) {
              anno = draw_anno;
              draw_anno = null;
            }
            
        }
        else {
            nn = RemoveSpecialChars(document.getElementById('objEnter').value);
            anno = main_handler.QueryToRest();
        }
        var re = /[a-zA-Z0-9]/;
        if(!re.test(nn)) {
            alert('Please enter an object name');
            return;
        }
        
    
        // Update old and new object names for logfile:
        submission_edited = 0;
        global_count++;
      
        // Insert data into XML:
        var html_str = '<object>';
        html_str += '<name>' + nn + '</name>';
        if(use_attributes) {
            html_str += '<occluded>' + new_occluded + '</occluded>';
            html_str += '<attributes>' + new_attributes + '</attributes>';
        }
        html_str += '<parts><hasparts></hasparts><ispartof></ispartof></parts>';
        var ts = 0;//GetTimeStamp();
        if(ts.length==20) html_str += '<date>' + ts + '</date>';
        html_str += '<id>' + anno.anno_id + '</id>';
        html_str += '<polygon>';
        html_str += '<username>' + username + '</username>';
        var t_str = '<t>';
        var x_str = '<x>';
        var y_str = '<y>';
        for (var fr = oVP.getcurrentFrame(); fr < oVP.getnumFrames(); fr++){
            if (fr > oVP.getcurrentFrame()){ 
                t_str += ', ';
                x_str += '; ';
                y_str += '; ';
            }
            t_str += fr;
            for(var jj=0; jj < anno.GetPtsX().length; jj++) {
                if (jj > 0){
                    x_str += ', ';
                    y_str += ', ';
                }
                x_str += anno.GetPtsX()[jj];
                y_str += anno.GetPtsY()[jj];
            }
        }
        t_str += '</t>';
        x_str += '</x>';
        y_str += '</y>';
        html_str += t_str;
        html_str += x_str;
        html_str += y_str;
        html_str += '<userlabeled>'+oVP.getcurrentFrame()+'</userlabeled>';
        html_str += '</polygon>';
        html_str += '<parts>'
        html_str += '<hasparts/>'
        html_str += '<ispartof/>'
        html_str += '</parts>'
        html_str += '</object>';
        $(LM_xml).children("annotation").append($(html_str));
        //ChangeLinkColorFG(anno.GetAnnoID());
        $('#select_canvas').css('z-index','0');
        $('#select_canvas_div').css('z-index','0');
        $('#'+this.polygon_id).remove();
          select_anno = anno;
          // var anno = main_canvas.DetachAnnotation(anno.anno_id);
        adjust_event = new AdjustEvent('select_canvas',anno.pts_x,anno.pts_y,LMgetObjectField(LM_xml,anno.anno_id,'name'),function(x,y,_editedControlPoints) {
          // Submit username:
          if(username_flag) submit_username();

          // Redraw polygon:
          anno = select_anno;
          anno.DrawPolygon(main_media.GetImRatio());

          // Set polygon (x,y) points:
          anno.pts_x = x;
          anno.pts_y = y;

          // Set global variable whether the control points have been edited:
          editedControlPoints = _editedControlPoints;
          // Submit annotation:
          StopEditEvent();
          
        },main_media.GetImRatio());
      // Start adjust event:
      adjust_event.StartEvent();
    };


    
}