I used Node.js to OCR "Meme Monday" threads
Tags: javascript, tutorial, programming, ai
Published: 2023-08-06 00:58:37

I love programming-related memes and jokes, and I'm sure you do as well. @ben's weekly "Meme Monday" posts are an amazing source for humor I always look forward to weekly.

## What we're building

We will build a simple project that outputs a markdown file with all the memes on a Meme Monday thread.  Each meme will be outputted with the [OCR (Optical character recognition)](https://en.wikipedia.org/wiki/Optical_character_recognition) -detected text.

OCR detection will be done with [Tesseract](https://github.com/tesseract-ocr/tesseract).

## Setup

Spin up a [Node.js Repl](https://replit.com/new/nodejs) on [Replit](https://replit.com).

### Installing Tesseract

If you run `tesseract` in the shell, you will notice the command does not exist since it isn't installed.

In the top-right-corner of the filetree, click the three dots and select "Show hidden files".

![Show hidden files filetree option](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y26239hotowkbm7v8b3v.png)

Navigate to the `replit.nix` configuration file and add `pkgs.tesseract4` to the package dependency list.

```nix
{ pkgs }: {
  deps = [
    pkgs.tesseract4
    pkgs.nodejs-18_x
    pkgs.nodePackages.typescript-language-server
    pkgs.yarn
    pkgs.replitPackages.jest
  ];
}
```

Run `tesseract` in the shell.  It should show some options now.

### Dependencies

Install `node-tesseract-ocr` and `node-fetch`.

```
npm install node-tesseract-ocr node-fetch
```

We're all set, let's get coding.

## Building the thing

Navigate to `index.js`.

Require/import the following dependencies at the top of the file.

```js
const tesseract = require("node-tesseract-ocr");
const fetch = require("node-fetch");
const fs = require("fs");
```

### Fetching article comments

Create an asynchronous function `fetchArticleComments` that takes a `slug` argument.

```js
const fetchArticleComments = async (slug) => {

}
```

Let's hit the dev.to API and get an article by its slug.  If the response fails, let's throw an error.

```js
if (!articleRes.ok) throw new Error("Failed to fetch article")

const article = await articleRes.json();
```

Derive the article's ID and fetch the article comments with it.  Return the comments if the response is successful.

```js
const fetchArticleComments = async (slug) => {
  const articleRes = await fetch("https://dev.to/api/articles/" + slug)

  if (!articleRes.ok) throw new Error("Failed to fetch article")

  const article = await articleRes.json();

  const commentsRes = await fetch("https://dev.to/api/comments?a_id=" + article.id);

  if (!commentsRes.ok) throw new Error("Failed to fetch comments")

  return await commentsRes.json();
}
```

### Extracting URLs

Create and call an asynchronous `main` function at the end of the file.

```js
async function main() {

}

main();
```

Within the `main` function, fetch the comments of a dev.to article and create a `urls` array in which we'll store the extracted URLs.

```js
const comments = await fetchArticleComments("ben/meme-monday-59gk");

// Embedded Image URLs found in the comments
const urls = [];
```

Create a `for` loop and iterate through the comments.  For each comment, let's use a regular expression to match an image URL from an image `src` prop and push it to `urls`.

```js
for (const comment of comments) {
  // Get embedded images from the comment
  const images = comment.body_html.match(/src=\"[^\"]+\.(jpg|png|webp|jpeg)\"/g);

  // Extract the image URLs from the embedded images
  if (images?.length) {
    const imageUrls = images.map(str => str.replace(/src="/, "").replace(/"/, ""));

    urls.push(...imageUrls);
  }
}
```

### OCR Text Extraction

Create an array variable `images` for storing URLs and the extracted OCR text.

```js
const images = [];
```

Create a `for` loop to iterate through `urls`.  Use `fetch` and `res.ok` to ensure that the image exists.

```js
 for (const i in urls) {
  const url = urls[i];

  // Make sure the image exists
  const res = await fetch(url);

  if (res.ok) {
      
  }
}
```

Within the `if (res.ok)` statement, use `await tesseract.recognize(url)` to get the text from the respective URL and push it to `images`.

```js
if (res.ok) {
  const text = await tesseract.recognize(url);
      
  images.push({
    url,
    text
  });

  console.log("Finished Processing URL", Number(i) + 1, "of", urls.length);
}
```

Finally, at the end of the `main` function, use `fs.writeFileSync` to write the changes to a file named `memes.md`.

```js
fs.writeFileSync(
  "memes.md",
  images
    .map(({ url, text }) => {
      // Sanitize the text to be an image alt by removing newlines and special markdown tokens
      const sanitizedText = text.replace(/\[|\]|\"/g, c => "\\" + c).replaceAll("\n", "");
      
      // Return the text followed by a markdown-formatted image
      return `${text}\n\n![${sanitizedText}](${url})`
    })
    .join("\n\n")
);
```

Run the Repl.  You should see as each image gets processed and at the end you will see a `memes.md` file full of the memes along with the OCR-extracted text.

If you use the Markdown tool, you can preview the output markdown file.

![Markdown tool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jr7ksob7rupnb785jtic.png)

![Markdown preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/snb884mkfp6cbz9hhelm.png)

---

And that's it! Thanks for reading

[Demo & Source Code](https://replit.com/@IroncladDev/OCR-Text-Example)

## Say hi 👋
 - [Github](https://github.com/IroncladDev)
 - [Twitter](https://twitter.com/IroncladDev)
 - [Website](https://connerow.dev)