批量处理

本文提供了关于在多个文件上运行OCRmyPDF或将其配置为由文件系统事件触发的服务的信息。

批量作业

考虑使用优秀的 GNU Parallel 来一次性对多个文件应用 OCRmyPDF。

无论是 parallel 还是 ocrmypdf 都会尝试使用所有可用的处理器。为了在不使系统过载的情况下最大化并行性,考虑使用 parallel -j 2 来限制并行运行的任务数量为两个。

此命令将在当前目录中对所有名为 *.pdf 的文件运行 ocrmypdf,并将它们写入先前创建的 output/ 文件夹中。它不会搜索子目录。

--tag 参数告诉 parallel 在打印消息时将文件名作为前缀打印出来,以便可以追踪任何错误到产生它们的文件。

parallel --tag -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf

OCRmyPDF 在解析和收集信息之前会自动修复 PDF 文件。

目录树

这将遍历目录树并对所有文件进行OCR处理,并在每次运行之间打印每个文件名:

find . -name '*.pdf' -printf '%p\n' -exec ocrmypdf '{}' '{}' \;

这只会一次运行一个ocrmypdf进程。这个变体使用 find来创建目录列表,并使用parallel来并行运行 ocrmypdf,再次在原地更新文件。

find . -name '*.pdf' | parallel --tag -j 2 ocrmypdf '{}' '{}'

在Windows批处理文件中,使用

for /r %%f in (*.pdf) do ocrmypdf %%f %%f

使用Docker容器时,您需要通过标准输入和输出进行流式传输:

find . -name '*.pdf' -print0 | xargs -0 | while read pdf; do
    pdfout=$(mktemp)
    docker run --rm -i jbarlow83/ocrmypdf - - <$pdf >$pdfout && cp $pdfout $pdf
done

示例脚本

此用户贡献的脚本还提供了一个批处理的示例。

misc/batch.py
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2016 findingorder <https://github.com/findingorder>
# SPDX-FileCopyrightText: 2024 nilsro <https://github.com/nilsro>
# SPDX-License-Identifier: MIT

"""Example of using ocrmypdf as a library in a script.

This script will recursively search a directory for PDF files and run OCR on
them. It will log the results. It runs OCR on every file, even if it already
has text. OCRmyPDF will detect files that already have text.

You should edit this script to meet your needs.
"""

from __future__ import annotations

import filecmp
import logging
import os
import posixpath
import shutil
import sys
from pathlib import Path

import ocrmypdf

# pylint: disable=logging-format-interpolation
# pylint: disable=logging-not-lazy


def filecompare(a, b):
    try:
        return filecmp.cmp(a, b, shallow=True)
    except FileNotFoundError:
        return False


script_dir = Path(__file__).parent
# set archive_dir to a path for backup original documents. Leave empty if not required.
archive_dir = "/pdfbak"

if len(sys.argv) > 1:
    start_dir = Path(sys.argv[1])
else:
    start_dir = Path(".")

if len(sys.argv) > 2:
    log_file = Path(sys.argv[2])
else:
    log_file = script_dir.with_name("ocr-tree.log")

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(message)s",
    filename=log_file,
    filemode="a",
)

logging.info(f"Start directory {start_dir}")

ocrmypdf.configure_logging(ocrmypdf.Verbosity.default)

for filename in start_dir.glob("**/*.pdf"):
    logging.info(f"Processing {filename}")
    if ocrmypdf.pdfa.file_claims_pdfa(filename)["pass"]:
        logging.info("Skipped document because it already contained text")
    else:
        archive_filename = archive_dir + str(filename)
        if len(archive_dir) > 0 and not filecompare(filename, archive_filename):
            logging.info(f"Archiving document to {archive_filename}")
            try:
                shutil.copy2(filename, posixpath.dirname(archive_filename))
            except OSError:
                os.makedirs(posixpath.dirname(archive_filename))
                shutil.copy2(filename, posixpath.dirname(archive_filename))
        try:
            result = ocrmypdf.ocr(filename, filename, deskew=True)
            logging.info(result)
        except ocrmypdf.exceptions.EncryptedPdfError:
            logging.info("Skipped document because it is encrypted")
        except ocrmypdf.exceptions.PriorOcrFoundError:
            logging.info("Skipped document because it already contained text")
        except ocrmypdf.exceptions.DigitalSignatureError:
            logging.info("Skipped document because it has a digital signature")
        except ocrmypdf.exceptions.TaggedPDFError:
            logging.info(
                "Skipped document because it does not need ocr as it is tagged"
            )
        except Exception:
            logging.error("Unhandled error occured")
        logging.info("OCR complete")

群晖 DiskStations

如果安装了Synology的Docker包,Synology DiskStations(网络附加存储设备)可以运行OCRmyPDF的Docker镜像。附上了一个脚本,用于解决在这些设备上使用OCRmyPDF时的一些特殊问题。

在编写此脚本时,它仅适用于基于x86的Synology产品。尚不清楚它是否适用于基于ARM的Synology产品。可能需要进一步调整以应对Synology相对有限的CPU和RAM。

misc/synology.py - Sample script for Synology DiskStations
#!/bin/env python3
# SPDX-FileCopyrightText: 2017 Enantiomerie
# SPDX-License-Identifier: MIT

"""Example OCRmyPDF for Synology NAS."""

from __future__ import annotations

# This script must be edited to meet your needs.
import logging
import os
import shutil
import subprocess
import sys
import time

# pylint: disable=logging-format-interpolation
# pylint: disable=logging-not-lazy

script_dir = os.path.dirname(os.path.realpath(__file__))
timestamp = time.strftime("%Y-%m-%d-%H%M_")
log_file = script_dir + '/' + timestamp + 'ocrmypdf.log'
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(message)s',
    filename=log_file,
    filemode='w',
)

start_dir = sys.argv[1] if len(sys.argv) > 1 else '.'

for dir_name, _subdirs, file_list in os.walk(start_dir):
    logging.info(dir_name)
    os.chdir(dir_name)
    for filename in file_list:
        file_stem, file_ext = os.path.splitext(filename)
        if file_ext != '.pdf':
            continue
        full_path = os.path.join(dir_name, filename)
        timestamp_ocr = time.strftime("%Y-%m-%d-%H%M_OCR_")
        filename_ocr = timestamp_ocr + file_stem + '.pdf'
        # create string for pdf processing
        # the script is processed as root user via chron
        cmd = [
            'docker',
            'run',
            '--rm',
            '-i',
            'jbarlow83/ocrmypdf',
            '--deskew',
            '-',
            '-',
        ]
        logging.info(cmd)
        full_path_ocr = os.path.join(dir_name, filename_ocr)
        with (
            open(filename, 'rb') as input_file,
            open(full_path_ocr, 'wb') as output_file,
        ):
            proc = subprocess.run(
                cmd,
                stdin=input_file,
                stdout=output_file,
                stderr=subprocess.PIPE,
                check=False,
                text=True,
                errors='ignore',
            )
        logging.info(proc.stderr)
        os.chmod(full_path_ocr, 0o664)
        os.chmod(full_path, 0o664)
        full_path_ocr_archive = sys.argv[2]
        full_path_archive = sys.argv[2] + '/no_ocr'
        shutil.move(full_path_ocr, full_path_ocr_archive)
        shutil.move(full_path, full_path_archive)
logging.info('Finished.\n')

大规模批处理作业

如果您有成千上万的文件需要处理,请联系作者。 与OCRmyPDF相关的咨询工作有助于资助这个开源项目, 我们感谢所有的咨询。

热门(已观看)文件夹

使用watcher.py监视的文件夹

OCRmyPDF 有一个名为 watcher.py 的文件夹监视器,目前包含在源代码分发中,但不是主程序的一部分。它可以本地使用,也可以在 Docker 容器中运行。本地实例通常能提供更好的性能。watcher.py 在所有平台上都能工作。

用户可能需要自定义脚本来满足他们的需求。

pip3 install ocrmypdf[watcher]

env OCR_INPUT_DIRECTORY=/mnt/input-pdfs \
    OCR_OUTPUT_DIRECTORY=/mnt/output-pdfs \
    OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1 \
    python3 watcher.py

可以配置一个网络扫描仪或扫描计算机,将文件放入监视的文件夹中。

使用 Docker 监控文件夹

OCRmyPDF Docker 镜像中包含了 watcher 服务。要运行它:

docker run \
    --volume <path to files to convert>:/input \
    --volume <path to store results>:/output \
    --volume <path to store processed originals>:/processed \
    --env OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1 \
    --env OCR_ON_SUCCESS_ARCHIVE=1 \
    --env OCR_DESKEW=1 \
    --env PYTHONUNBUFFERED=1 \
    --interactive --tty --entrypoint python3 \
    jbarlow83/ocrmypdf \
    watcher.py

该服务将监视匹配 /input/\*.pdf 的文件, 将其转换为 OCRed PDF 并保存到 /output/ 中,并将处理后的 原始文件移动到 /processed。该图像的参数为:

watcher.py parameters for Docker

参数

描述

--volume to files to convert>:/input

放置在此位置的文件将被OCR处理

--volume to store results>:/output

这是存储OCR处理后的文件的位置

--volume to store processed originals>:/processed

将处理后的原始文件存档在这里

--env OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1

定义环境变量 OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1 以将文件放置在输出目录中的 {output}/{year}/{month}/{filename}

--env OCR_ON_SUCCESS_ARCHIVE=1

定义环境变量 OCR_ON_SUCCESS_ARCHIVE 以移动处理过的原始文件

--env OCR_DESKEW=1

定义环境变量 OCR_DESKEW 以对歪斜的输入PDF应用去歪斜处理

--env PYTHONBUFFERED=1

这将强制 STDOUT 不进行缓冲,并允许你在 docker 日志中查看消息

此服务依赖于轮询来检查文件系统的更改。它可能不适用于某些环境,例如在慢速网络上共享的文件系统。

可以使用诸如Docker Compose之类的配置管理器来确保服务始终可用。

misc/docker-compose.example.yml
# SPDX-FileCopyrightText: 2022 James R. Barlow
# SPDX-License-Identifier: MIT
---
version: "3.3"
services:
  ocrmypdf:
    restart: always
    container_name: ocrmypdf
    image: jbarlow83/ocrmypdf
    volumes:
      - "/media/scan:/input"
      - "/mnt/scan:/output"
    environment:
      - OCR_OUTPUT_DIRECTORY_YEAR_MONTH=0
    user: "<SET TO YOUR USER ID>:<SET TO YOUR GROUP ID>"
    entrypoint: python3
    command: watcher.py

注意事项

  • watchmedo 可能无法在网络文件系统上正常工作,这取决于文件系统客户端和服务器的能力。

  • 这个简单的配方不会过滤文件系统事件的类型, 因此文件复制、删除和移动,以及目录操作,都会 被发送到ocrmypdf,在几种情况下会产生错误。如果你正在做除了复制文件到 它之外的其他操作,请禁用你的监视文件夹。

  • 如果源目录和目标目录相同,watchmedo 可能会创建一个无限循环。

  • 在BSD、FreeBSD和旧版本的macOS上,你可能需要增加文件描述符的数量以监控更多的文件,使用ulimit -n 1024来监控最多1024个文件的文件夹。

替代方案

  • 在Linux上,可以配置systemd用户服务来自动对一组文件执行OCR。

  • Watchmanwatchmedo 的一个更强大的替代品。

macOS Automator

您可以在macOS上使用Automator应用程序来创建工作流程或快速操作。在您的工作流程中使用运行Shell脚本操作。在Automator的上下文中,PATH可能与您的终端的PATH设置不同;您可能需要显式设置PATH以包含ocrmypdf。以下示例可以作为起点:

Example macOS Automator workflow

您可以自定义发送给ocrmypdf的命令。