how to convert my animated SVG in to GIF fromat

48 Views Asked by At
  • I didn't found any efficient solution right now,

  • I'm able to do it with puppeteer and FFmpeg but it takes time and also did not get the resolution that I wanted.

currently, this approach launches svg in the browser through puppeteer and then takes snapshots in PNGs and after that combines those PNGs and makes gif but this process is resource-intensive and also not get the solution that i wanted

this is my current approach, it is working but not efficient


let fs = require("fs");
let child_process = require("child_process");
const { exec } = require("child_process");

let puppeteer = require("puppeteer");

const usage = "usage: node index.js <svgPath> <duration> <fps> <outDir>";
const imgExtention = "png";
const imgType = "png";

async function main() {
  let [svgPath, duration, fps, outDir] = process.argv;

  svgPath = "react-flow-canvas.svg";
  duration = 4;
  fps = 10;
  outDir = "output";
  if (outDir === undefined) {
    console.error("outDir is not defined");
    console.log(usage);
    process.exit(2);
  }
  const svg = fs.readFileSync(svgPath, "utf-8");

  duration = parseFloat(duration);
  fps = parseInt(fps);
  console.log("duration: " + duration + " s, fps: " + fps);
  const totalFrames = Math.floor(fps * duration);
  const digits = Math.floor(Math.log10(totalFrames)) + 1;
  console.log("totalFrames: " + totalFrames);

  process.chdir(outDir);
  await createFrames(svg, fps, totalFrames, digits);
  convertToMP4(fps, totalFrames, digits);
}

async function createFrames(svg, fps, totalFrames, digits) {
  svg = svg.replace("--play-state: running;", "--play-state: paused;");

  let browser = await puppeteer.launch({
    headless: true,
    args: ["--no-sandbox", "--font-render-hinting=none"],
  });

  let page = await browser.newPage();
  await page.goto("about:blank");
  await page.setContent(svg);

  let renderSettings = {
    type: imgType,
    omitBackground: false,
  };

  console.log("creating frames");
  for (let i = 1; i <= totalFrames; ++i) {
    let result = await page.evaluate(function (startVal) {
      document
        .getElementsByTagName("svg")[0]
        .style.setProperty("--start", startVal);
    }, "" + (i - 1) / fps + "s");

    await page.waitForTimeout(1);

    let outputElem = await page.$("svg");
    let prefix = ("" + i).padStart(digits, "0");
    renderSettings.path = prefix + "." + imgExtention;
    await outputElem.screenshot(renderSettings);
    if (i % fps === 0 || i === totalFrames) {
      console.log("progress: " + prefix + " / " + totalFrames);
    }
  }

  await browser.close();
  return totalFrames, digits;
}

function convertToMP4(fps, totalFrames, digits) {
  console.log("running ffmpeg");
  let output = child_process.execFileSync(
    "ffmpeg",
    [
      "-hide_banner",
      "-loglevel",
      "warning",
      "-y",
      "-framerate",
      "" + fps,
      "-i",
      "%0" + digits + "d." + imgExtention,
      "-c:v",
      "libx264",
      "-vf",
      "fps=" + fps,
      "-pix_fmt",
      "yuv420p",
      "output.mp4",
    ],
    { encoding: "utf8" }
  );
  console.log(output);

  exec("ffmpeg -i output.mp4 -qscale 200 output.gif");
}

main();

Please help me to do that in an efficient manner

1

There are 1 best solutions below

0
Yagnik Vadaliya On

I got better solution for this

const puppeteer = require("puppeteer");
const { PuppeteerScreenRecorder } = require("puppeteer-screen-recorder");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
let fs = require("fs");

const wait = (ms) => new Promise((res) => setTimeout(res, ms));

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    args: ["--no-sandbox", "--font-render-hinting=none"],
  });
  try {
    const page = await browser.newPage();
    await page.setViewport({
      width: 1920,
      height: 1080,
      deviceScaleFactor: 3,
    });
    const svg = fs.readFileSync("react-flow-canvas.svg", "utf-8");
    // await page.goto("about:blank");
    await page.setContent(svg);
    const Config = {
      fps: 25,
    };

    const recorder = new PuppeteerScreenRecorder(page, Config);

    // await page.goto("https://tailwindcss.com/");

    await recorder.start("video.mp4");
    await wait(1700);
    // await page.waitForTimeout(1);
    await recorder.stop();

    await exec("ffmpeg -i video.mp4 -qscale 0 animated.gif");
  } catch (e) {
    console.log(e);
  } finally {
    await browser.close();
  }
})();