Tools,

Scribe.js OCR, tested on scanned PDFs in Node

1,895

of 1,955 words, default settings

Get the script

OCR a scanned PDF in Node

15 s for 10 pages, 2 workers

23 MB language data, first run

In a folder with scan.pdf:

npm i scribe.js-ocr@0.18.2
node scribe-ocr.mjs scan.pdf > scan.txt

scribe-ocr.mjs:

// scribe-ocr.mjs: OCR a scanned PDF or image with Scribe.js.
// node scribe-ocr.mjs scan.pdf > scan.txt
import scribe from "scribe.js-ocr";

scribe.opt.workerN = 2; // the default is up to 8 in Node
const text = await scribe.extractText([process.argv[2]]);
console.log(text);
await scribe.terminate();

On a PDF with a typed cover page, it kept all 13 cover words too.

scribe extract gives 0 words

10 scanned pages, 1,955 words
Command Words Exit code
scribe extract scan.pdf out.txt 0 0
scribe recognize scan.pdf --output out -w 2 1,882 0

extract only reads text that is already in the PDF. A scan has none, so out.txt is empty. recognize runs OCR and writes a searchable copy to out/scan.pdf.

Reading text back from out/scan.pdf

scribe extract read 1,882 words. pdftotext read 1,776, because it split words such as "f or". With pdftotext -raw it read 1,870.

Scribe.js vs tesseract.js

Same 10 pages, 2 workers, language data saved
Engine Words Time
Scribe.js, default 1,895 14 s
Scribe.js, mode: "speed" 1,880 8 s
tesseract.js 7.0.0 1,876 18 s

The default found 15 more words than speed mode, and took about 70% longer. tesseract.js does not read PDFs, so it got the 10 page JPEGs.

The first run needs the internet

Scribe.js downloads eng.traineddata from cdn.jsdelivr.net and saves it in the folder you run from. With no network and no saved file, it stopped:

TypeError: fetch failed

With the file in the folder, the same run with no network found 1,894 words. Run from that folder, or copy the file.

AGPL: read this before you ship it

Scribe.js is AGPL 3.0. Its own docs say that to use it in your program, front end or server, you must publish your program under AGPL 3.0 or a compatible license, or buy a license.

tesseract.js is Apache 2.0.

Need text with nothing to install?

Our Actor takes PDF, JPG and PNG files and returns Markdown, from one web request. On the same 10 pages, inside a PDF, it found 1,874 words. Scribe.js found more.

curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
  | jq -r '.[].markdown' > book.md

It costs $0.006 a page.

How I tested it
  • Pages: the 10 scans of OCR in n8n, proofread Wikisource book pages, 1,955 true words. Each page is one 300 ppi JPEG.
  • scribe.js-ocr 0.18.2 and tesseract.js 7.0.0 from npm, Node 22, on an Apple Silicon Mac. 2 workers in every run, at lowest priority.
  • Times: 2 rounds with the language data saved. The rounds matched within half a second. The first-run time includes the download.
  • Score: true words found in order, with the scorer of the tessdata test. 7 default runs found 1,894 or 1,895.
  • No network: macOS sandbox-exec with all network access denied.
  • The Actor: one real run, build 0.1.65, on November 12. Its file holds the same 10 pages plus a typed cover page.
What I did not test
  • Scribe.js in the browser, and its paid cloud OCR options.
  • Windows and Linux.
  • Phone photos, handwriting, and languages other than English.
Sources

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Does Scribe.js work on scanned PDFs?

Yes, with extractText in Node or the recognize command. On 10 scanned pages, extractText found 1,895 of 1,955 words. The scribe extract command does no OCR: it gave 0 words, with exit code 0.

Is Scribe.js more accurate than tesseract.js?

A little, in this test. On the same 10 pages, Scribe.js found 1,895 words and tesseract.js 7.0.0 found 1,876. Scribe.js speed mode found 1,880.

Can Scribe.js run offline?

After the first run. It downloads eng.traineddata (23 MB) and saves it in the folder you run from. With no network and no saved file, it stopped with TypeError: fetch failed.