Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the recommended way to save image data from a puppeteer screenshot?

I am in the process of making an application for plotting running routes on a map and saving them to a mongodb database.

Currently, I am using puppeteer to visit a route in my application and passing the coordinates as a query string to the map component. Once the map is loaded, I take the screenshot, convert the returned Buffer into a base64 encoded string, save that to the database, and use the string to show the image on the frontend.

The middleware for the whole process looks like this:

exports.screenshotMap = async (req, res, next) => {
  try {
    const { lineFeatures } = req.body;
    const browser = await puppeteer.launch({
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });

    // open new browser
    const page = await browser.newPage();

    // create array of coordinates from geojson features
    const coords = lineFeatures.map(line => line.geometry.coordinates);
    // reduce to 2D array of [lat, lon] coords
    const flattenedCoords = coords.reduce((accum, arr) => {
      return accum.concat(arr);
    }, []);
    // Stringify coords before using them as query string
    const coordsStr = JSON.stringify(flattenedCoords);

    // goto page with map sending coordintaes along
    await page.goto(`http://localhost:3000/test?coords=${coordsStr}`, {
      waitUntil: 'networkidle0',
    });

    // wait for map to load, call onLoad callback, and set state to make the h1 visible
   await page.waitForSelector('h1');
    // wait one more second to make sure all tiles for the map are loaded. Longer routes can require significantly more tiles
    await page.waitFor(1000);

    const image = await page.screenshot({
      type: 'jpeg',
      quality: 100,
      clip: {
        x: 0,
        y: 70,
        width: 640,
        height: 360,
      },
      omitBackground: true,
    });

    await browser.close();
    // convert buffer to base64 string
    const base64Image = await image.toString('base64');
    // attach to request object to be used in the next middleware
    req.image = base64Image;
    next();

  } catch (err) {
    res.status(400).send(err);
  }
};

This approach works, however I am wondering if there is a better way. I have read the storing the Buffer data is better for database memory purposes, as the base64 strings are very long. Would a better way be to save the Buffer data and convert it to an encoded string before sending it back to the client? Is there a recommended way for dealing with this kind of data? I am interested in hearing others thoughts and approaches.

like image 388
erik_g Avatar asked Jun 20 '19 21:06

erik_g


1 Answers

The solution I came up with was to save the buffer returned from the screenshot to an S3 bucket and then store the unique identifier url to my data base.

Here is the middleware for screen taking the screenshot:

import puppeteer from 'puppeteer';
//import chromium from 'chrome-aws-lambda';

const takeMapImage = handler => async (req, res) => {
  try {
    const { lines } = req.body;

    // should work for most node projects
    const browser = await puppeteer.launch({
       args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });

    // if youre using lambda functions
    // const browser = await chromium.puppeteer.launch({
    //  executablePath: await chromium.executablePath,
    // args: chromium.args,
    // defaultViewport: chromium.defaultViewport,
    // headless: chromium.headless,
    // });

    // open new browser
    const page = await browser.newPage();

    const url = 'https://yourwebstieurl.com';

    await page.goto(
      `${url}`,
      {
        waitUntil: 'networkidle0',
      }
    );

    // provide some waiting time if needed
    await page.waitFor(1000);

   // image buffer returned from screenshot
    const imageBuffer = await page.screenshot({
      type: 'jpeg',
      quality: 100,
      clip: {
        x: 0,
        y: 0,
        width: 640,
        height: 360,
      },
      omitBackground: true,
    });


    // attach to request object to be used in the next middleware
    req.buffer = imageBuffer;

    next();

  } catch (err) {
    console.log(err);
    return res
      .status(422)
      .send({ message: 'there was an error taking picture' });
  }
};

Then the middleware for saving the image to s3. You will need to create a bucket and get credentials:

import AWS from 'aws-sdk';
import uuid from 'uuid/v1';

// create your s3instance with your credentials
const s3 = new AWS.S3({
  accessKeyId: process.env.S3_ACCESS_KEY_ID,
  secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
});

const saveImageToS3 = handler => async (req, res) => {
  try {
    // use user id as identifier in S3 bucket
    const { id } = req.user;
    const { buffer } = req;

    // s3 file name
    const key = `${id}/${uuid()}.jpeg`;

    const params = {
      Bucket: 'your-bucket-name',
      Key: key,
      Body: buffer,
      ContentEncoding: 'base64',
      ContentType: 'image/jpeg',
    };

    // upload to bucket
    const response = await s3.upload(params).promise();
    // pass url to next middleware to save to db
    req.image = response.Location;
    next();
  } catch (e) {
    console.log(e);
    return res.status(422).json({ message: 'user error saving image to s3' });
  }
};

like image 164
erik_g Avatar answered Nov 15 '22 09:11

erik_g