-->

Exporting a video in p5.js

2019-03-31 22:07发布

问题:

I am creating a simple animation program in p5.js. When a user clicks the save button, I want to download a video of the animation.

I have an object called frames where each key is labelled frame_1, frame_2 and so on. The value associated with each key is an array of line segments that makes up that frame.

I am trying to think of an approach to take this data and create an mp4 video. p5.js has a built in save function that I thought might be helpful but it is not a full solution on its own. I could save each frame as an individual image and then somehow stitch those images together on the client side but I have yet to find a solution to this.

Any other approaches would be great as well. The only requirement is that it is done client side.

回答1:

Since p5.js is built on the Canvas API, in modern browsers, you can use a MediaRecorder to do this job.

const btn = document.querySelector('button'),
  chunks = [];

function record() {
  chunks.length = 0;
  let stream = document.querySelector('canvas').captureStream(30),
    recorder = new MediaRecorder(stream);
  recorder.ondataavailable = e => {
    if (e.data.size) {
      chunks.push(e.data);
    }
  };
  recorder.onstop = exportVideo;
  btn.onclick = e => {
    recorder.stop();
    btn.textContent = 'start recording';
    btn.onclick = record;
  };
  recorder.start();
  btn.textContent = 'stop recording';
}

function exportVideo(e) {
  var blob = new Blob(chunks);
  var vid = document.createElement('video');
  vid.id = 'recorded'
  vid.controls = true;
  vid.src = URL.createObjectURL(blob);
  document.body.appendChild(vid);
  vid.play();
}
btn.onclick = record;

// taken from pr.js docs
var x, y;

function setup() {
  createCanvas(300, 200);
  // Starts in the middle
  x = width / 2;
  y = height;
}

function draw() {
  background(200);

  // Draw a circle
  stroke(50);
  fill(100);
  ellipse(x, y, 24, 24);

  // Jiggling randomly on the horizontal axis
  x = x + random(-1, 1);
  // Moving up at a constant speed
  y = y - 1;

  // Reset to the bottom
  if (y < 0) {
    y = height;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.min.js"></script>
<button>start recording</button><br>



回答2:

When you have a problem that you don't know how to approach, the first thing you need to do is break that problem down into smaller sub-problems. Then you can take on those smaller problems one at a time. Recommended reading: How to Program

I will say that what you're describing is not exactly trivial. JavaScript has a lot of built-in security features that make things like manipulating local files difficult- after all, you wouldn't want any website you visit to have access to your hard drive.

One solution would be to send the images to a server and have the server create the video. But if you're just starting out with P5.js, creating the server is going to be a huge job.

A basic Google search found this library which seems to support saving canvas animations (which is what P5.js is) to video. I also found this question which leads to this library.

Another option would be to switch to regular Processing and export your animation as an application instead of a webapp. Then you could do whatever you want with local files. You'd lose the ability to embed in a browser though.



回答3:

As you specified in the comments that a gif would also work, here is a solution:

Below is a sample p5 sketch that records canvas animation and turns it into a gif, using gif.js.

Works in browsers supporting: Web Workers, File API and Typed Arrays.

I've provided this code so you can get an idea of how to use this library because not much documentation is provided for it and I had a hard time myself figuring it out.

var cnv;

var gif, recording = false;

function setup() {
    cnv = createCanvas(400, 400);

    var start_rec = createButton("Start Recording");
    start_rec.mousePressed(saveVid);

    var stop_rec = createButton("Stop Recording");
    stop_rec.mousePressed(saveVid);

    start_rec.position(500, 500);
    stop_rec.position(650, 500);

    setupGIF();
}

function saveVid() {
    recording = !recording;
    if (!recording) {
        gif.render();
    }
}
var x = 0;
var y = 0;

function draw() {
    background(51);
    fill(255);
    ellipse(x, y, 20, 20);
    x++;
    y++;

    if (recording) {
        gif.addFrame(cnv.elt, {
            delay: 1,
            copy: true
        });
    }
}

function setupGIF() {
    gif = new GIF({
        workers: 5,
        quality: 20
    });
    gif.on('finished', function(blob) {
        window.open(URL.createObjectURL(blob));
    });
}

More Info :

This sketch starts recording frames when you click start_rec and stops when you hit stop_rec, in your sketch you might want to control things differently, but keep in mind that addFrame only adds one frame to the gif so you need to call it in the draw function to add multiple frames, you can pass in an ImageElement, a CanvasElement or a CanvasContext along with other optional parameters.

In the gif.on function, you can specify a callback function to do whatever you like with the gif.

If you want to fine tune settings of the gif, like quality, repeat, background, you can read more here. Hope this helps!



回答4:

ccapture works well with p5.js to achieve the goal of recording what's displaying on a canvas.

Here is a demo of ccapture working with p5.js. The source code comes with the demo.

This method won't output laggy videos because it is not recording what you see on the screen, which can be laggy. Instead, it writes every frame into the video and tells the videos to play at a fixed frame rate. So even if it takes seconds to calculate just one frame, the output video will play smoothly without showing any delay between frames.

However, there is one caveat though. This method only works with Chrome.