MagicMirror² v2.5.0 is available! For more information about this release, check out this topic.

MMM-RTSPStream Snapshot image resolution appears low, and blurry.



  • I’ve hit a bit of a snag with implementation of the MMM-RTSPStream module. I have 2 cabled ubiquiti external cameras displaying using omxplayer with only a minor delay of about 3 seconds. These are turned on from my Domoticz server, using an API call to MMM-remote to “SHOW” the module whenever Domoticz picks up a doorbell signal using an 433 Mhz RF receiver. Unlike most others who are having problems, I have the actual streaming working satisfactorily.
    Unfortunately the jpeg snapshot displayed appears compressed or scaled down and is not a clear representation of the actual image.
    I have confirmed that the url for the snapshot presents a full resolution image ( i have worked with the ubiquiti cameras for some time). With my limited css knowledge and a lot of Googlefoo I have used the developer tools in chrome and firefox to confirm that the jpg data loaded is a full resolution image. ( i can copy pasta the data back to a file and name it xxx.jpg and view it).
    Where is the magic happening that scales the snapshot down?
    If i grab pass the url for the snapshot to another module such as MMM-Alert the image displays as a small but clear and non-blurry image.
    Somebody give me a clue please!



  • i had a similar problem displaying images on my mirror,
    they were zoomed in (clipping all 4 sides(landscape pics) or top and bottom (pics in portrait mode))…

    the dimension ratios of the pic did not match the dimension ratios of the display space (div)…

    so I had to scale them myself…

    i found this routine to calculate the scale of the image, thru searching

    ScaleImage: function(srcwidth, srcheight, targetwidth, targetheight, fLetterBox) {
    
        var result = { width: 0, height: 0, fScaleToTargetWidth: true };
    
        if ((srcwidth  targetwidth);
        if (fScaleOnWidth) {
            fScaleOnWidth = fLetterBox;
        }
        else {
           fScaleOnWidth = !fLetterBox;
        }
    
        if (fScaleOnWidth) {
            result.width = Math.floor(scaleX1);
            result.height = Math.floor(scaleY1);
            result.fScaleToTargetWidth = true;
        }
        else {
            result.width = Math.floor(scaleX2);
            result.height = Math.floor(scaleY2);
            result.fScaleToTargetWidth = false;
        }
        result.targetleft = Math.floor((targetwidth - result.width) / 2);
        result.targettop = Math.floor((targetheight - result.height) / 2);
    
        return result;
    },
    

    then changed the display code, by using an onload handler

    to eliminate lots of empty display time,

    • only create DIV once,
    • load the image hidden,
    • once completely loaded,
      ** calculate scale size
      ** adjust image display size,
      ** then make visible
    getDom: function() {
        
        // if wrapper div not yet created
        if(this.wrapper ==null)
          // create it
          this.wrapper = document.createElement("div");
    
        // get the size of the margin, if any, we want to be full screen
        var m = window.getComputedStyle(document.body,null).getPropertyValue('margin-top');
        // set the style for the containing div
        Log.log("body size="+document.body.clientWidth+"+"+document.body.clientHeight+" margin="+m);
    //  use style to get fullscreen 
        if(this.config.position==='fullscreen')
          this.wrapper.style.class=this.config.position+".above";
    
      // instead of trying to force it manually
      //	this.wrapper.style.position = "absolute";
      //	this.wrapper.style.left = 0;
      //	this.wrapper.style.top = parseInt(this.wrapper.style.height)+60; //document.body.clientHeight+(parseInt(m))+"px";
      //	this.wrapper.style.opacity = self.config.opacity;
      //	this.wrapper.style.width  = document.body.clientWidth+(parseInt(m)*2)+"px";
      //	this.wrapper.style.height = document.body.clientHeight+(parseInt(m)*2)+"px";
        
        this.wrapper.style.backgroundColor = this.config.backgroundColor;
        
        this.wrapper.style.border = "none";
        this.wrapper.style.margin = "0px";
    			
      //	Log.log("body size="+this.wrapper.style.width+"+"+this.wrapper.style.height+" pos="+this.wrapper.style.top);
    
        var photoImage = this.randomPhoto();
        var img = null;
        if (photoImage) {
    
          // create img tag element
          img = document.createElement("img");
    
          // set default position, corrected in onload handler
          img.style.left = 0+"px";
          img.style.top = document.body.clientHeight+(parseInt(m)*2);  
          img.style.position="relative"; //(to the containing div)
    
          img.src = photoImage.url;
    
          // make invisible
          img.style.opacity = 0;
          // append this image to the div
          this.wrapper.appendChild(img);
    
          // set the onload event handler
          img.onload= function (evt) {
    
            // get the image of the event
            var img = evt.currentTarget;
            Log.log("image loaded="+img.src+" size="+img.width+":"+img.height);
    
              // what's the size of this image and it's parent
              var w = img.width;
            var h = img.height;
            var tw = document.body.clientWidth+(parseInt(this.m)*2);  // use img.parent if not fullscreen
             var th = document.body.clientHeight+(parseInt(this.m)*2);
    
            // compute the new size and offsets
            var result = this.self.ScaleImage(w, h, tw, th, true);
    
            // adjust the image size
            img.width = result.width;
            img.height = result.height;
    
            Log.log("image setting size to "+result.width+":"+result.height);
            Log.log("image setting top to "+result.targetleft+":"+result.targettop);
    
            // adjust the image position
            img.style.left = result.targetleft+"px";
            img.style.top = result.targettop+"px";	
            img.style.opacity =	this.self.config.opacity;
            img.style.transition = "opacity 1.25s";
    
            // if another image was already displayed
            if( this.self.wrapper.firstChild!=this.self.wrapper.lastChild)
            {
              // hide it
              this.self.wrapper.firstChild.style.opacity=0;	
              // remove the image element from the div
              this.self.wrapper.removeChild(this.self.wrapper.firstChild);
            }
    
          }.bind({self: this, m:m});
          
        }
        return this.wrapper;
      },
    
    

  • Module Developer

    Sorry you’re having issues. To give a brief background: showing snapshots instead of video was an afterthought in this module. I created the module because there were modules to show MJPEG streams and to show snapshots, but nothing that could play the video from an RTSP source which is what my cameras put out. I added snapshots in because the original ffmpeg version was a CPU and RAM hog and I needed to show something when I didn’t need to stream the video (sounds like the situation described above). Now fast-forward a year, and I’ve found that all I really need for my main mirror is snapshots. 😞

    Anyways, when I added the snapshots to get around some CORS issue I was having, the module grabs the snapshots from the node_helper.js on the “server” end, converts the jpeg to base64 and sends over the socket to the front end/browser, where it draws the image on the canvas (relevant code) trying to make use of the same elements which the original ffmpeg version used to draw the video on the canvas. I am sure this is not the most efficient or best way to render the images correctly which I’m sure is why you’re seeing some quality issues.

    If you guys want to take a look at the code and have a solution, I’d be happy to accept a PR and correct the problem.



  • @sdetweil @shbatm Thanks for the info. i’ll have a look at integrating/fixing it then figuring out how to do a pull request… or i might cheat and just use another module for the snapshot and use MMM-RTSPStream to render over the top using the omxplayer, that way the remote virwer( if any) can always see a higher res snapshot that keeps updating, and the the local viewer gets live video and audio.


  • Module Developer

    Some options I’ve thought about or tried but did not fully implement:

    1. node_helper grabs image and saves to disk, front end grabs the image. This was the first method, but I went away from it because it involves (a) repeatedly writing to the SD card and (b) requires two timed actions to stay relatively in sync (front and back end).
    2. Directly accessing the image from camera via the front end. Works for some, and would also allow MJPEG streams to be used, but for certain cameras this gives a CORS violation error. This is what I did in my Octoprint module.
    3. Use an iframe overlay/underlay in the same position as the canvas tag, and then same as #2. Should get around CORS and would be the best option that I know of, just haven’t gone back and implemented.


  • @shbatm said in MMM-RTSPStream Snapshot image resolution appears low, and blurry.:

    requires two timed actions to stay relatively in sync (front and back end).

    you can use event handlers for both… so don’t need to do timed…



  • @sdetweil @shbatm
    Well it seems this is a bit beyond me at this point, I have a bit of a learning curve to get past if want to sort it out. Im more of a hardware guy. 🙂 At this point I’ve got an separate iframe MMM-module pulling the cam image and the omx player overlay covers it when called. not the best solution but it’s within my skill set. thank you all for your input.