Reputation: 11
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
Upvotes: 1
Views: 475
Reputation: 11
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();
}
})();
Upvotes: 0