{"id":50979802,"url":"https://github.com/privatenumber/mac-ocr","last_synced_at":"2026-07-25T23:30:30.527Z","repository":{"id":366013836,"uuid":"1268405871","full_name":"privatenumber/mac-ocr","owner":"privatenumber","description":"macOS CLI for OCR and searchable PDFs using Apple's Vision framework","archived":false,"fork":false,"pushed_at":"2026-07-03T08:04:30.000Z","size":2795,"stargazers_count":446,"open_issues_count":0,"forks_count":21,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2026-07-03T10:10:01.836Z","etag":null,"topics":["apple-vision","cli","command-line-tool","image-to-text","macos","nodejs","ocr","pdf","searchable-pdf","swift","text-recognition","vision-framework"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/privatenumber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"privatenumber"}},"created_at":"2026-06-13T13:49:56.000Z","updated_at":"2026-07-03T08:04:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/privatenumber/mac-ocr","commit_stats":null,"previous_names":["privatenumber/mac-ocr"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/privatenumber/mac-ocr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Fmac-ocr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Fmac-ocr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Fmac-ocr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Fmac-ocr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/privatenumber","download_url":"https://codeload.github.com/privatenumber/mac-ocr/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/privatenumber%2Fmac-ocr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35896124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-25T02:00:06.922Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple-vision","cli","command-line-tool","image-to-text","macos","nodejs","ocr","pdf","searchable-pdf","swift","text-recognition","vision-framework"],"created_at":"2026-06-19T13:00:27.368Z","updated_at":"2026-07-25T23:30:30.521Z","avatar_url":"https://github.com/privatenumber.png","language":"Swift","funding_links":["https://github.com/sponsors/privatenumber"],"categories":["Swift"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\t\u003cimg src=\".github/logo.webp\" width=\"200\" alt=\"mac-ocr logo\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003emac-ocr\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n\tA macOS command-line tool that reads text from images and PDFs, and creates searchable PDFs.\u003cbr\u003e\n\tRuns entirely on your Mac with Apple's \u003ca href=\"https://developer.apple.com/documentation/vision\"\u003eVision framework\u003c/a\u003e; nothing is uploaded.\n\u003c/p\u003e\n\n\u003e [!TIP]\n\u003e Useful for AI agents too: instead of spending vision tokens reading documents, an agent can run `mac-ocr` locally for free. A [skill](#agent-skills) is bundled so agents know how to use it.\n\n### Features\n\n- **Read text from an image:** `mac-ocr photo.png`\n- **Read text from many images:** `mac-ocr *.png`\n- **Stream text from a PDF, page by page:** `mac-ocr scan.pdf --format jsonl`\n- **Turn an image into a searchable PDF:** `mac-ocr searchable-pdf photo.png` → `photo.ocr.pdf`\n- **Add a selectable text layer to a scanned PDF:** `mac-ocr searchable-pdf scan.pdf` → `scan.ocr.pdf`\n\n## Install\n\n```sh\nnpm install -g mac-ocr\n```\n\nOr run it without installing:\n\n```sh\nnpx mac-ocr receipt.jpg\n```\n\n**Requirements:** macOS 10.15+. The npm package ships a prebuilt universal binary, so no Xcode or Swift toolchain is needed.\n\n## Recognize text\n\nOCR is the default action — you don't need a subcommand:\n\n```sh\nmac-ocr receipt.jpg                 # text → stdout\nmac-ocr page1.png page2.png         # multiple images\nmac-ocr scan.pdf                    # multi-page PDF\ncat screenshot.png | mac-ocr        # stdin\nmac-ocr https://example.com/a.png   # URL (simple GET)\n```\n\nDefault output is plain text. Use JSON when you need bounding boxes, confidence, or page metadata:\n\n```sh\nmac-ocr receipt.jpg --format json\nmac-ocr document.pdf --format jsonl   # one JSON object per page, streamed\n```\n\nPDF pages stream as they're recognized, so with a large document you see the first page's text right away.\n\n### Save text to files\n\n```sh\nmac-ocr ~/Screenshots/*.png -o '[dir]/[name].txt'   # a .txt next to each image\nmac-ocr scan.pdf -o notes.md                        # recognized text to a chosen .txt/.md file\nmac-ocr receipts/*.pdf -o out/                      # one file per input in out/\ngrep -rli \"invoice\" ~/Screenshots                    # then search with normal tools\n```\n\n`-o` takes a file, a directory (`out/`), or a filename template ([all placeholders](docs/CLI.md#writing-to-files)). Quote templates, since `[…]` is a glob pattern in zsh. Whatever the extension, the content is the plain recognized text.\n\n## Create a searchable PDF\n\n`searchable-pdf` takes a PDF or an image and writes a PDF that looks identical to the source but whose text is selectable and searchable. By default it writes `[name].ocr.pdf` next to each input — one searchable PDF per input:\n\n```sh\nmac-ocr searchable-pdf scan.pdf            # writes scan.ocr.pdf\nmac-ocr searchable-pdf photo.jpg            # image → one-page photo.ocr.pdf\nmac-ocr searchable-pdf *.pdf                # writes \u003cname\u003e.ocr.pdf for each\nmac-ocr searchable-pdf --merge -o lease.pdf page1.jpg page2.jpg\n```\n\nUse `-o` to control the destination — a directory, a `[name]` template, a fixed file, or `-` for stdout:\n\n```sh\nmac-ocr searchable-pdf scan.pdf -o out/              # out/scan.ocr.pdf\nmac-ocr searchable-pdf scan.pdf -o '[name]-ocr.pdf'  # scan-ocr.pdf\nmac-ocr searchable-pdf scan.pdf -o searchable.pdf    # fixed path\nmac-ocr searchable-pdf scan.pdf -o - \u003e scan.pdf      # stdout\n```\n\nA fixed path or `-` (stdout) takes a single input in non-merge mode; for multiple per-input outputs use a directory or a `[name]` template.\n\nPass `--merge` to combine multiple file/URL inputs into one searchable PDF. Merged pages follow the exact argument order you pass; `mac-ocr` never sorts or reorders inputs.\n\nImage inputs are sized from embedded DPI metadata when available. Images without usable DPI metadata fall back to 72 DPI (1px = 1pt).\n\n### Partitioned OCR\n\nSearchable PDFs use `--ocr-strategy auto` by default. Vision can miss small labels when it analyzes a full high-resolution page at once, even though the same text is readable in a tighter crop. Auto mode starts with full-page OCR, then runs a partitioned pass only for large pages with small or missing text: it recursively splits regions along their longer axis until text is large enough or the region is below the calibrated size floor.\n\nIn dogfooding on a high-resolution five-page scan, partitioned OCR recovered small form labels the full-page pass missed while keeping the generated PDF around 7 MB. Large partitioned runs may take longer because Vision processes regions serially. Use `--ocr-strategy standard` to opt out, or `--ocr-strategy partitioned` to force the partitioned pass for eligible pages. Auto mode skips partitioning when `--roi` is set; forced `partitioned` mode cannot be combined with `--roi`.\n\nIn non-merge mode, pages that already have selectable text are skipped — only scanned pages get OCR. A PDF that needs no OCR at all passes through unchanged. To OCR every page regardless, pass `--ocr-all-pages`. The finer points (what survives a rewrite, how \"already has text\" is decided) are in [docs/CLI.md](docs/CLI.md#searchable-pdf).\n\nIn an interactive terminal you get a live `[page/total]` progress counter. Piped or redirected runs are silent on success, so scripts stay clean.\n\n## Options\n\nBoth OCR and `searchable-pdf` accept the recognition options:\n\n| Flag | Effect |\n|------|--------|\n| `--fast` | Faster, lower-accuracy recognition ([details](docs/CLI.md#recognition-levels)) |\n| `--password \u003cpassword\u003e` | Password for an encrypted PDF (or set `MAC_OCR_PDF_PASSWORD`) |\n| `-l, --language \u003ccode\u003e` | Recognition language (BCP-47, repeatable). e.g. `-l en-US -l ja-JP` |\n| `-c, --confidence \u003c0–1\u003e` | Drop observations below this confidence |\n| `-w, --custom-words \u003cword\u003e` | Add custom vocabulary (repeatable) |\n| `--custom-words-file \u003cpath\u003e` | Custom vocabulary file, one word per line |\n| `--no-language-correction` | Disable language correction |\n| `--min-text-height \u003c0–1\u003e` | Ignore text shorter than this fraction of image height |\n| `--pdf-dpi \u003cauto\\|72–600\u003e` | PDF rasterization DPI (default `auto`) |\n| `--roi \u003cx,y,w,h\u003e` | Region of interest: restrict recognition to a normalized region (top-left origin) |\n\n### `mac-ocr \u003cfile\u003e`\n\n| Flag | Effect |\n|------|--------|\n| `-f, --format \u003ctext\\|json\\|jsonl\u003e` | Output format (default `text`) |\n| `-o, --output \u003cpath\u003e` | Output path, directory, or template (`[name]`, `[ext]`, `[dir]`, `[page]`). Default: stdout. Any extension — e.g. `.txt` or `.md`. |\n| `--max-candidates \u003c1–10\u003e` | Alternative text candidates per observation |\n\n### `mac-ocr searchable-pdf \u003cfile\u003e`\n\n| Flag | Effect |\n|------|--------|\n| `-o, --output \u003cdest\u003e` | Output path, `[name]` template, directory, or `-` for stdout. Default: `[name].ocr.pdf` next to each input. |\n| `--ocr-all-pages` | OCR every page, including pages that already have selectable text (skipped by default) |\n| `--ocr-strategy \u003cauto\\|standard\\|partitioned\u003e` | Searchable PDF OCR strategy. `auto` may run a partitioned second pass for large pages with small text; `standard` uses full-page OCR only. |\n| `--merge` | Combine inputs into one searchable PDF in argument order. Requires `-o \u003cfile.pdf\u003e` or `-o -`. |\n| `--image-quality \u003c0–1\u003e` | Visible image layer quality for image inputs. OCR still uses the original full-resolution image; PDF inputs are not recompressed. |\n| `--image-page-dpi \u003c36–2400\u003e` | DPI to use for image input page sizing. OCR still uses the original full-resolution image; PDF inputs are unaffected. |\n| `--image-downsample-dpi \u003c36–2400\u003e` | Maximum DPI for the visible image layer of image inputs. OCR and page size are unaffected; PDF inputs are not downsampled. |\n\nList the recognition languages available on your macOS version with `mac-ocr languages` (add `--fast` for the fast recognizer's set).\n\nSee [docs/CLI.md](docs/CLI.md) for the full reference — every command and flag, plus the JSON output schema.\n\n## Node.js API\n\nThe same package exposes a typed, promise-based API that wraps the binary. Inputs are image or PDF **bytes** — read files or fetch URLs in your own code and pass the bytes:\n\n```sh\nnpm install mac-ocr\n```\n\n```ts\nimport fs from 'node:fs/promises'\nimport { ocr, createSearchablePdf, supportedLanguages } from 'mac-ocr'\n\n// Recognize text in an image or single-page PDF\nconst result = await ocr(await fs.readFile('receipt.jpg'))\nconsole.log(result.text)\nfor (const { text, confidence, boundingBox } of result.observations) { /* … */ }\n\n// Multi-page PDF: stream pages as they finish…\nfor await (const page of ocr.pages(await fs.readFile('book.pdf'))) {\n    console.log(page.page, '/', page.pageCount, page.text)\n}\n// …or collect the whole thing into an array\nconst pages = await Array.fromAsync(ocr.pages(await fs.readFile('book.pdf')))\n\n// Build a searchable PDF (returns the PDF bytes)\nconst pdf = await createSearchablePdf(await fs.readFile('scan.pdf'), { fast: true })\nawait fs.writeFile('scan.ocr.pdf', pdf)\n\n// Recognition languages supported on this macOS version (for ocr and createSearchablePdf)\nconst languages = await supportedLanguages()\n```\n\nOptions mirror the CLI flags (like `{ fast: true }` above), plus an `AbortSignal` for cancellation. Failures throw a `MacOcrError` with a `kind` you can branch on. See [docs/NODE.md](docs/NODE.md) for every option, the result types, and error handling.\n\n## How it works\n\n`mac-ocr` is a native Swift binary built on Apple's Vision framework (`VNRecognizeTextRequest`). Recognition happens entirely on-device — nothing is uploaded. The searchable-PDF layer is invisible text drawn with Core Graphics + Core Text, placed word by word where Vision found each word.\n\n## Agent Skills\n\nThe package bundles an [agent skill](https://agentskills.io) covering the CLI and Node API — set up [`skills-npm`](https://github.com/antfu/skills-npm) in your project and coding agents discover it automatically.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprivatenumber%2Fmac-ocr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprivatenumber%2Fmac-ocr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprivatenumber%2Fmac-ocr/lists"}