seed icon indicating copy to clipboard operation
seed copied to clipboard

Allow users to export animated GIFs

Open fdb opened this issue 7 years ago • 30 comments

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:

  • The output are HTML / SVG elements, not a canvas tag, so we can't use something like GIF.js
  • The element we want to convert to GIF can be any element, of any size. The size can even change between frames.

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.

fdb avatar Feb 19 '18 14:02 fdb

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

kunal-mohta avatar Feb 19 '18 15:02 kunal-mohta

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.

fdb avatar Feb 19 '18 16:02 fdb

Okay, so basically making an API?

kunal-mohta avatar Feb 19 '18 16:02 kunal-mohta

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 avatar Feb 20 '18 16:02 fdb

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

fdb avatar Feb 20 '18 20:02 fdb

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 avatar Feb 22 '18 02:02 kunal-mohta

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.

kunal-mohta avatar Feb 22 '18 04:02 kunal-mohta

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

fdb avatar Feb 22 '18 13:02 fdb

Let me try.

kunal-mohta avatar Feb 22 '18 13:02 kunal-mohta

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

kunal-mohta avatar Feb 22 '18 13:02 kunal-mohta

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 avatar Feb 22 '18 13:02 fdb

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.

fdb avatar Feb 22 '18 13:02 fdb

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

kunal-mohta avatar Feb 22 '18 13:02 kunal-mohta

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

$ node -v
v9.4.0

fdb avatar Feb 22 '18 13:02 fdb

Mine is v8.9.0. :worried:

kunal-mohta avatar Feb 22 '18 13:02 kunal-mohta

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

fdb avatar Feb 22 '18 14:02 fdb

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

kunal-mohta avatar Feb 22 '18 14:02 kunal-mohta

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.

fdb avatar Feb 22 '18 15:02 fdb

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

kunal-mohta avatar Feb 23 '18 12:02 kunal-mohta

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();
})();

fdb avatar Feb 23 '18 12:02 fdb

Still no success :confused:

kunal-mohta avatar Feb 23 '18 12:02 kunal-mohta

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

fdb avatar Feb 23 '18 12:02 fdb

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

kunal-mohta avatar Feb 23 '18 12:02 kunal-mohta

@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?

kunal-mohta avatar Mar 03 '18 08:03 kunal-mohta

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

fdb avatar Mar 03 '18 18:03 fdb

@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. 🤔

kunal-mohta avatar Mar 03 '18 19:03 kunal-mohta

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.

fdb avatar Mar 05 '18 12:03 fdb

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?

kunal-mohta avatar Mar 05 '18 12:03 kunal-mohta

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.

fdb avatar Mar 05 '18 13:03 fdb

Ohh! I get it now. Very nice! 👍

kunal-mohta avatar Mar 05 '18 13:03 kunal-mohta