nodebox / seed

Procedural Content Generator
https://seed.emrg.be/
MIT License
22 stars 8 forks source link

Allow users to export animated GIFs #16

Open fdb opened 6 years ago

fdb commented 6 years ago

When creating an animation, it would be nice if users could export the animation as an animated GIF.

This is challenging for a number of reasons:

One idea is to create a server-side renderer that could take seed sketches and render them in a headless browser, like Headless Chrome or PhantomJS. This would work as a webservice: we request a URL and it returns a GIF image.

kunal-mohta commented 6 years ago

Are you planning to render the animation, then screencast it and finally convert it into a GIF using some other webservice?

fdb commented 6 years ago

I would suggest creating a special URL route that only shows the output (like the embed view, but without the source editor and navigation bar), then use Chrome Headless to screencapture the page, then use something like GIF.js to generate a GIF file out of that.

This requires setting up a custom webservice (preferably using Heroku or Docker) that can convert sketches to GIF on-demand.

kunal-mohta commented 6 years ago

Okay, so basically making an API?

fdb commented 6 years ago

Yes, a basic webservice that takes in a Seed URL or Sketch address and outputs a GIF image. This could be the API address:

https://seedgif.herokuapp.com/create?sketch=-L4uLB99JzFHMPcVoiTm

fdb commented 6 years ago

Here's some documentation on running Headless Chrome on Heroku: https://timleland.com/headless-chrome-on-heroku/

The official Heroku buildpack looks promising: https://elements.heroku.com/buildpacks/minted/heroku-buildpack-chrome-headless

kunal-mohta commented 6 years ago

I have had a look over the links that you provided. It seems like the first focus should be on being able to make and run the app locally.

kunal-mohta commented 6 years ago

I was trying headless chrome locally using Puppeteer but I am facing this error screenshot 2018-02-22 09 51 19

My code is this

screenshot 2018-02-22 09 55 03

Are you familiar with this kind of errors? I am not being able to figure a way out of this.

fdb commented 6 years ago

No idea. Could you try a different site? Maybe google.com does not support headless browsing?

kunal-mohta commented 6 years ago

Let me try.

kunal-mohta commented 6 years ago

Nope, got the same error at https://seed.emrg.be/

fdb commented 6 years ago

Works for me:

seed

Here's the code:

const puppeteer = require('puppeteer');

(async() => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage();
    await page.goto('https://seed.emrg.be/');
    await page.screenshot({path: 'seed.png'});
    await browser.close();
})();
fdb commented 6 years ago

The main issue now is that the actual sketch page loads the script async, so it shows the "loading..." indicator when screenshotting the page:

seed

I've also linked issue #32 since we don't want to screenshot the entire UI, just the viewer portion.

kunal-mohta commented 6 years ago

Wait, how are you able to work with async - await syntax in Node.js without try - catch phrase. Doesn't it report UnhandledPromiseRejectionWarning?

fdb commented 6 years ago

I don't seem to have any issues with it. I'm using:

$ node -v
v9.4.0
kunal-mohta commented 6 years ago

Mine is v8.9.0. :worried:

fdb commented 6 years ago

You could try using nvm to see if it's the version that causing issues.

kunal-mohta commented 6 years ago

I updated to v9.5.0. Still gives the same error.

fdb commented 6 years ago

The ECONNRESET is an error that comes back from the network stack. It looks like the requests are blocked on the network level somehow — perhaps overly aggressive filtering by some router / firewall?

I think you should try with different sites.

kunal-mohta commented 6 years ago

I tried several sites with no hope. Could you send me your package.json file please?

fdb commented 6 years ago

Here's mine:

{
  "name": "headless",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "puppeteer": "1.1.0"
  }
}

And here's the index.js:

const puppeteer = require('puppeteer');

(async() => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage();
    await page.goto('https://seed.emrg.be/sketch/-L4uLB99JzFHMPcVoiTm');
    await page.screenshot({path: 'seed.png'});
    await browser.close();
})();
kunal-mohta commented 6 years ago

Still no success :confused:

fdb commented 6 years ago

Does it work on a different site? E.g. the site of your school?

kunal-mohta commented 6 years ago

Nope, I tried that too. Didn't work. I will have to try it on one of my peers' system.

kunal-mohta commented 6 years ago

@fdb I was finally able to get puppeteer running (though was not able to solve the above issue). Now I am able to take screenshots of the browser. However, have you decided upon the method to be used to record the screen, or is it yet to be figured out?

fdb commented 6 years ago

Since the built-in animations are entirely deterministic, you could calculate the duration * FPS to get the amount of frames. Then, loop through each frame and set the internal frame counter and render out each image in sequence. Then, make a GIF out of this image sequence.

Initially it would be good to have this working for just a static image import, which is useful by itself.

Here's a crappy mockup of how that UI might look:

seed-export-menu

kunal-mohta commented 6 years ago

@fdb Yes, the static image part can be achieved now. However, I doubt if puppeteer is so accurate to keep up with the FPS of animations. 🤔

fdb commented 6 years ago

For the animation I would just render out an image sequence with the frame counter set to a specific value. So we don't render animations realtime; instead we export individual images at the exact frame time we want. We can then stitch these image sequences together using GIF.js or ffmpeg.

kunal-mohta commented 6 years ago

Okay, I understand your method. But how can we account for the delay that will exist between the script calling the screenshot method and Puppeteer actually taking the screenshot?

fdb commented 6 years ago

None of it needs to be realtime. We can just pass the frame we want to export as a URL parameter, e.g. https://seed.emrg.be/view/SKETCH_ID_HERE?seed=ABC&frame=5 Then do this repeatedly for every frame.

kunal-mohta commented 6 years ago

Ohh! I get it now. Very nice! 👍