43

When I am trying to get a screenshot and save it as PNG before uploading video to server, I am having the following problem

enter image description here

I hope you can solve my problem ...

/*Output image show view*/
$('#file_browse').change(function(e){
    getVideo(this);
});

var capbtn = document.querySelector('#video_capture');
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var w, h, ratio;

video.addEventListener('loadedmetadata', function() {
    ratio = video.videoWidth / video.videoHeight;
    w = video.videoWidth - 100;
    h = parseInt(w / ratio, 10);
    canvas.width = w;
    canvas.height = h;           
}, false);

capbtn.addEventListener("click", function(){
    context.fillRect(0, 0, w, h);
    context.drawImage(video, 0, 0, w, h);
    var objImageData = canvas.toDataURL("data:image/png;");  
});

function getVideo(input) {
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            var video = document.getElementsByTagName('video')[0];
            var sources = video.getElementsByTagName('source');
            sources[0].src = e.target.result;
            video.load();
            video.style.display="block";
        }
        reader.readAsDataURL(input.files[0]);
    }
}
<input id="video_capture" type="submit" value="Capture" />
<video id="video_view" controls>
    <source src="movie.mp4" type="video/mp4">
</video>
<canvas width="300" height="300"></canvas>
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Kemal
  • 431
  • 1
  • 4
  • 3
  • using windowURL.createObjectURL(input.files[0]) would be much smoother/faster and potentially more generous with perms. – dandavis Sep 09 '14 at 21:27
  • Chromium gave me a more detailed error than Firefox: "Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported." https://stackoverflow.com/questions/22710627/tainted-canvases-may-not-be-exported – baptx Nov 15 '22 at 15:36

6 Answers6

50

Sounds like a CORS issue.

The Video is on a different origin than the web server.

If you can get the video to include an "Access-Control-Allow-Origin: *" header in the response, and you can set video.crossorigin = "Anonymous", then you can probably pull this off.

I used Charles Web Proxy to add the header to any image or video I wanted to work with.

See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image

See Also https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes

Here's a Fiddle working with an Image: http://jsfiddle.net/mcepc44p/2/

var canvas = document.getElementById("canvas").getContext("2d");

var button = document.getElementById("button");

var image = new Image();
image.crossOrigin = "anonymous";  // This enables CORS
image.onload = function (event) {
    try {
        canvas.drawImage(image, 0, 0, 200, 200);
        button.download = "cat.png";
        button.href = canvas.canvas.toDataURL();        
    } catch (e) {
        alert(e);
    }
};
image.src = "https://i.chzbgr.com/maxW500/1691290368/h07F7F378/"

Is this what you're looking for?

StephenKC
  • 613
  • 1
  • 6
  • 6
  • 12
    This should have been the accepted answer. The below line from the snippet makes all the difference. `image.crossOrigin = "anonymous"; // This enables CORS` – Sudi Jan 14 '16 at 23:56
  • 7
    This shouldn't have been the accepted answer as it doesn't work with videos as the OP asked – joshcomley Feb 06 '16 at 17:54
  • God bless you, you just save my life ;) – Mehdi Sabouri Dec 07 '16 at 06:14
  • This workaround is uneeded for sites like *i.chzbgr.com* that have fully permissive CORS set. You can just use an `` tag. ... For *i.imgur.com*, though, this workaround seems needed because the server restricts CORS methods to `GET, OPTIONS`. – Brock Adams Nov 04 '17 at 07:42
  • 1
    It does not work for videos for me. I have set the CORS headers and confirmed using curl -I that the header is really set. I want to extract a frame from a video and show it in an image. I get the error when calling toDataURL() on video frame. – Simon Hessner Apr 16 '20 at 01:49
  • @joshcomley Do you know how to get it working for videos? – Simon Hessner Apr 16 '20 at 01:50
  • Found a solution in the answers below. Just set `crossOrigin="anonymous"` directly on the video tag. No need to set it manually via JavaScript. After that I was able to call `toDataFrame` on a canvas that shows the current frame of the video. – Simon Hessner Apr 16 '20 at 02:09
  • FWIW you can also set this directly in html like `` – deanWombourne May 13 '20 at 21:10
16

It's because of the Same Origin Policy. Basically, you're not allowed to access the video data of something loaded from another origin/site using a canvas.

Drawing video data on the canvas sets the origin-clean flag to false, which stops you from getting the image data in any way.

See toDataURL for more information.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
tcooc
  • 20,629
  • 3
  • 39
  • 57
11

As well, took some experimentation to reach that conclusion, on iOS, looks the position .crossOrigin parameter position should be put before the .src

// Webkit will throw security error when image used on canvas and canvas.toDataUrl()

return new Promise((resolve, reject) => {
  let image = new Image();
  img.onload = () => {resolve(image)}
  img.src = `${url}?${Date.now()}`;
  img.crossOrigin = ""
})

// Webkit will not throw security error when image used on canvas and canvas.toDataUrl()

return new Promise((resolve, reject) => {
  let img = new Image()
  img.onload = () => {resolve(img)}
  img.crossOrigin = ''
  img.src = `${url}?${Date.now()}`
})
Dotgreg
  • 333
  • 3
  • 9
1

Hi i have the same problem, in a system where i show videos, my users have to create a thumb when they select a video, but then Security Error.

This solution only works if you can modify the server, then you can send the video or font or etc with CORS. So you have to edit httpd.conf( Apache configuration from the server where you pull the videos).

<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        <FilesMatch "\.(mp4)$">
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        </FilesMatch>
    </IfModule>
</IfModule>

and in your webpage or app, to the video tag add: crossOrigin="Anonymous"

This is a fragment of my code, after that all works again..

document.getElementById('video_thumb').innerHTML =
             '<img src="'+data_uri+'"  width="250"
                        height="250px" crossOrigin="Anonymous">';       

With this, canvas.toDataURL() don't trow error

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
1

Setting the attribute before the src solved the problem for our drawing app: https://www.trumpgraffitimemes.com/

useEffect(() => {
    const img = new Image();
    if (picdatanew.length !== 0) {
      img.setAttribute("crossorigin", "anonymous");
      img.src = picdatanew[picID].webformatURL;
    
      setCanvasSize({
        width: picdatanew[picID].webformatWidth,
        height: picdatanew[picID].webformatHeight,
      });
      setPicturedata(img);
      setWholedata([]);


      setTimeout(() => {
        contextRef.current.drawImage(
          img,
          0,
          0,
          picdatanew[picID].webformatWidth,
          picdatanew[picID].webformatHeight
        );
        var image = canvasRef.current.toDataURL("image/jpg");
        setMyImage(image);
      }, 2000);
    }
  }, [picID, picdatanew]);
d_-
  • 1,391
  • 2
  • 19
  • 37
0

In my case, the problem was related to the presence of icons that weren't coming from my site as well as the presence of a Select input. Your video container may contain the same items. To solve, I had to hide the select inputs and icons before the screenshot was taken, then show them again after the screenshot was taken. Then, the canvas.toDataURL worked perfectly and didn't throw that security error.

Before Screenshot:

$("select[name='example']").add(".external-icon").hide();

After Screenshot:

$("select[name='example']").add(".external-icon").show();
codeymcgoo
  • 565
  • 4
  • 8