High-performance document conversion tools powered by Rust + PyO3.
Project description
dy_docs_tools
docs_tools 是一个基于 Rust + PyO3 的文档转换工具包,当前支持:
PDF -> 图片PDF -> PDF(先转为受控图片,再重建回 PDF)图片 -> PDFDOCX -> PDFDOCX -> 图片
1. 环境准备
Rust 工具链
在 Windows PowerShell 中安装 Rust:
winget install Rustlang.Rustup
安装完成后重新打开终端,并执行:
cargo --version
rustc --version
Python 构建工具
python -m pip install --upgrade pip
python -m pip install maturin
外部转换器
pdftoppm(来自 Poppler),用于PDF -> 图片soffice(来自 LibreOffice),用于DOCX -> PDF- 如果
pdftoppm不在PATH中,库会在 Windows 和 Linux 的常见安装目录中自动查找
Windows 示例安装命令:
winget install oschwartz10612.Poppler
winget install TheDocumentFoundation.LibreOffice
确认命令可用:
pdftoppm -v
soffice --version
2. 构建与安装
在项目根目录执行:
maturin develop
如果更倾向于构建 wheel:
maturin build --release
python -m pip install .\target\wheels\*.whl
Linux 发布基线说明:
- 已发布的 Linux wheel 覆盖
manylinux_2_24(glibc >= 2.24) - 额外提供
manylinux_2_36(glibc >= 2.36)版本 - GitHub Actions 发布流程默认基于
manylinux_2_24构建 manylinux_2_36wheel 在 Debian 12 容器中通过maturin + auditwheel构建- 如果要复现 Linux 发布环境,优先使用
cibuildwheel或与manylinux_2_24一致的容器,不要直接在更新的宿主机系统上构建发布轮子
快速验证
python scripts\verify.py
如果包导入失败,脚本会自动尝试加载 target/wheels 中最新生成的 wheel。
3. Python 接口
from docs_tools import (
close_converter_service,
pdf_to_pdf,
pdf_to_image,
image_to_pdf,
docx_to_pdf,
docx_to_image,
get_converter_service,
)
# PDF -> 图片
images = pdf_to_image(
"a.pdf",
"out",
dpi=300,
fmt="png",
prefix="page",
max_area=36_000_000,
max_size_mb=10,
)
# PDF -> PDF(默认 fmt 为 jpg)
pdf_out = pdf_to_pdf(
"a.pdf",
"a_rebuilt.pdf",
dpi=300,
fmt="jpg",
max_area=36_000_000,
max_size_mb=10,
)
# 图片 -> PDF
pdf_path = image_to_pdf(["1.png", "2.png"], "merged.pdf", max_area=36_000_000, max_size_mb=10)
pdf_bytes = image_to_pdf(["1.png", "2.png"], return_bytes=True)
# DOCX -> PDF(默认 fmt 为 jpg)
pdf_path = docx_to_pdf("a.docx", "out/a.pdf", dpi=300, fmt="jpg", max_area=36_000_000, max_size_mb=10)
# DOCX -> PDF,并启用受限重编码(默认关闭)
pdf_path_limited = docx_to_pdf(
"a.docx",
"out/a_limited.pdf",
apply_constraints=True,
dpi=300,
fmt="jpg",
max_area=36_000_000,
max_size_mb=10,
)
pdf_bytes2 = docx_to_pdf("a.docx", return_bytes=True)
# DOCX -> 图片
images = docx_to_image("a.docx", "out", dpi=200, fmt="jpg", max_area=36_000_000, max_size_mb=10)
image_bytes_list = docx_to_image("a.docx", dpi=200, fmt="jpg", return_bytes=True)
# 持久化工作线程池,用于提升批量吞吐
service = get_converter_service(workers=4)
try:
# 异步提交并获取句柄
h1 = service.submit_to_images("a.pdf", "out/a", 200, "png", "page", max_area=36_000_000, max_size_mb=10)
h2 = service.submit_to_pdf("a.docx", "out/a_from_service.pdf")
h3 = service.submit_to_pdf(
"a.docx",
"out/auto_from_docx.pdf",
apply_constraints=True,
dpi=300,
fmt="jpg",
max_area=36_000_000,
max_size_mb=10,
)
if not h1.done():
print("h1 仍在运行")
images = h1.result() # 阻塞直到完成
pdf_path = h2.result(10_000) # 超时单位为毫秒
pdf_path2 = h3.result(10_000)
finally:
close_converter_service()
图片输出限制:
- 所有转换接口都支持:
pdf_to_image、docx_to_image、image_to_pdf、docx_to_pdf、pdf_to_pdf,以及对应的池化版本 - 参数为
max_area(像素面积)和max_size_mb(MB) - 默认值分别为
36_000_000和10 - 当图片超过限制时,会自动缩放或压缩,直到满足约束
返回模式:
- 路径模式:默认返回输出路径或路径列表
- 字节模式:传入
return_bytes=True
调试日志:
- 设置
DOCS_TOOLS_DEBUG=1,可在stderr中输出更详细的处理日志,方便分析快捷路径、栅格化、约束收敛和 PDF 重建过程
4. 优化与压测结论
4.1 当前版本已落地的优化
- 基准脚本和压测脚本支持
--in-flight滑窗提交,避免一次性把任务全部压入队列 - 对 Poppler 和 LibreOffice 外部进程做全局并发限制,减少过度竞争
- 优先按可信扩展名判断输入类型,减少内容嗅探开销
- PDF 重建时减少重复图片解码和重复读取尺寸信息
return_bytes/data_url路径改为内存构建 PDF,去掉临时文件往返- 保留扫描件快路径和并行图片转码路径
simple-scanJPEG 默认质量保持为95
4.2 测试数据集信息
- 数据集目录:
C:\Users\PC\pros\python\tests - 文件总数:
10 - 总大小:
27,515,936字节(约26.24 MiB) - 本次 README 中的最新结果基于 wheel:
target\wheels\docs_tools-0.1.0-cp37-abi3-win_amd64.whl
| 类型 | 数量 | 总字节 | 占比 |
|---|---|---|---|
.pdf |
4 |
21,061,866 |
76.54% |
.jpg |
5 |
5,627,337 |
20.45% |
[无扩展名] |
1 |
826,733 |
3.00% |
- 最大文件:
505344.pdf - 文件大小:
19,430,977字节(约18.53 MiB) - 页数:
6 - 占整个测试集字节量:
70.62% - 结论:
505344.pdf是这个目录下最主要的性能瓶颈文件
4.3 最新连续压测结果(return_mode=path,dpi=300,page_dpi=300)
- 结果目录:
target\stress_loop_monitor_page300_latest_path - 汇总文件:
target\stress_loop_monitor_page300_latest_path\summary.json - 明细文件:
target\stress_loop_monitor_page300_latest_path\details.json - 压测参数:
workers=4、in_flight=4、fmt=jpg、rounds=10
| 指标 | 数值 |
|---|---|
| 总轮数 | 10 |
| 每轮文件数 | 10 |
| 总任务数 | 100 |
| 成功 / 失败 | 100 / 0 |
| 总耗时 | 47.171s |
| 平均每轮耗时 | 4.717s |
| 单轮最短耗时 | 4.200s |
| 单轮最长耗时 | 4.985s |
| 平均吞吐 | 2.120 files/s |
| Python 自身 RSS 起止 | 21.81 MB -> 24.01 MB |
| Python 自身峰值 RSS | 63.109 MB |
| 根进程峰值 RSS | 59.934 MB |
| 进程树峰值 RSS | 729.738 MB |
- 结论:连续 10 轮压测全部成功,Python 自身常驻内存变化较小,峰值主要来自外部转换子进程
4.4 大文件 505344.pdf 重点指标
- 输入文件:
C:\Users\PC\pros\python\tests\505344.pdf - 10 轮平均耗时:
4.696371s - P50 耗时:
4.778077s - 最快 / 最慢:
4.175831s / 4.971845s - 输出大小:
3,432,157字节(约3.27 MiB) - 相对源文件减小:
82.34% - 占平均轮耗时:
99.57% - 样例输出:
target\stress_loop_monitor_page300_latest_path\outputs\505344.round10.pdf
补充说明:
- 同目录下其余较慢 PDF 中,
505976.pdf的 10 轮平均耗时为2.483662s - 另一份扫描票据类 PDF 的 10 轮平均耗时为
2.481054s - 在这组测试数据中,
505344.pdf明显决定了整轮尾延迟
4.5 dpi=150 补充压测结果
- 结果目录:
target\stress_loop_monitor_page150_latest_path - 汇总文件:
target\stress_loop_monitor_page150_latest_path\summary.json - 明细文件:
target\stress_loop_monitor_page150_latest_path\details.json - 压测参数:
workers=4、in_flight=4、fmt=jpg、rounds=1、page_dpi=300
| 指标 | 数值 |
|---|---|
| 单轮总耗时 | 4.302403s |
| 吞吐 | 2.323 files/s |
| Python 自身峰值 RSS | 59.492 MB |
| 进程树峰值 RSS | 611.133 MB |
505344.pdf 单轮耗时 |
4.278890s |
505344.pdf 输出大小 |
3,432,157 字节 |
505344.pdf 样例输出 |
target\stress_loop_monitor_page150_latest_path\outputs\505344.round01.pdf |
- 结论:把
dpi从300调到150后,这个大文件的耗时和输出大小都没有出现明显下降,说明当前瓶颈并不主要由栅格 DPI 本身决定
4.6 505344.pdf 的分辨率、压缩与清晰度说明
- 源页面宽度:
2355.91 pt(约32.72 inch) - 输出页面宽度:
564.96 pt(约7.85 inch) - 源页图宽度:
2354 px - 输出页图宽度:
2354 px - 源有效 DPI:约
71.94 - 输出有效 DPI:
300
关键结论:
- 当前快路径没有把
505344.pdf的页图像素放大 300 dpi来自物理页面尺寸缩小,不是把页图重采样到更高像素- 如果沿用源 PDF 的
MediaBox,有效 DPI 会继续接近72,而不是300
JPEG 与画质说明:
simple-scan的默认 JPEG 质量是95- 本次
505344.pdf测试没有触发max_size_mb=10的二次压缩阈值,因此没有额外降低 JPEG 质量 - 抽取页图总字节约为
18.09 MB -> 3.27 MB - 平均
PSNR:43.13 dB - 平均绝对像素差:
1.07 / 255 - 综合判断:这是轻微有损压缩,文本和票据类内容的可读性变化较小
4.7 复现命令
下面这组命令可直接复现当前 README 中的核心性能数据:
# 构建最新 wheel
python -m maturin build --release -o target\wheels
# 单次目录基准:顺序提交
python scripts\benchmark_submit_to_pdf.py `
--input-dir C:\Users\PC\pros\python\tests `
--output-dir target\benchmark_seq_page300 `
--dpi 300 --page-dpi 300 --fmt jpg `
--workers 4 --in-flight 1 `
--csv target\benchmark_seq_page300\results.csv
# 单次目录基准:并发滑窗提交
python scripts\benchmark_submit_to_pdf.py `
--input-dir C:\Users\PC\pros\python\tests `
--output-dir target\benchmark_conc_page300 `
--dpi 300 --page-dpi 300 --fmt jpg `
--workers 4 --in-flight 4 `
--csv target\benchmark_conc_page300\results.csv
# 连续 10 轮压测(README 当前主结果)
python scripts\stress_submit_to_pdf_monitor.py `
--input-dir C:\Users\PC\pros\python\tests `
--output-dir target\stress_loop_monitor_page300_latest_path `
--rounds 10 `
--dpi 300 --page-dpi 300 --fmt jpg `
--workers 4 --in-flight 4
# dpi=150 补充对比
python scripts\stress_submit_to_pdf_monitor.py `
--input-dir C:\Users\PC\pros\python\tests `
--output-dir target\stress_loop_monitor_page150_latest_path `
--rounds 1 `
--dpi 150 --page-dpi 300 --fmt jpg `
--workers 4 --in-flight 4
5. CI 与发布
- CI 工作流:
.github/workflows/ci.yml- 构建 Linux 的 abi3 wheel
- Linux wheel 默认基于
manylinux_2_24(glibc >= 2.24) - 额外构建
manylinux_2_36(glibc >= 2.36) - 同时构建源码分发包
sdist
- 发布工作流:
.github/workflows/publish.yml- 通过
v0.1.0这类 git tag 触发 - 将所有构建产物上传到 PyPI
- Linux wheel 基线固定为
manylinux_2_24 - 同时上传
manylinux_2_36版本
- 通过
推荐的 PyPI 配置:
- 优先使用 GitHub 仓库 / 工作流对应的 Trusted Publishing(OIDC)
- 如果使用 API Token 方式,需要在发布步骤中切换为 token 认证,并在仓库密钥中配置
PYPI_API_TOKEN
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dy_docs_tools-0.1.6.tar.gz.
File metadata
- Download URL: dy_docs_tools-0.1.6.tar.gz
- Upload date:
- Size: 82.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
301732f7e36b9631a0df12d5b931d8dc177bfa48ba75df31edb2e28cc0fde11b
|
|
| MD5 |
5d9a534e0ae1fe075b812d18946887f7
|
|
| BLAKE2b-256 |
52b6d2e98b8f448e1a1e2fea855d0ca140c5adff8d6013c6a41ec16d1c74f452
|
Provenance
The following attestation bundles were made for dy_docs_tools-0.1.6.tar.gz:
Publisher:
publish.yml on ClassesOver/docs_tools
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dy_docs_tools-0.1.6.tar.gz -
Subject digest:
301732f7e36b9631a0df12d5b931d8dc177bfa48ba75df31edb2e28cc0fde11b - Sigstore transparency entry: 1592212917
- Sigstore integration time:
-
Permalink:
ClassesOver/docs_tools@63c04cd3749d66313bd71f2bd1340c6139d42bcc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ClassesOver
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@63c04cd3749d66313bd71f2bd1340c6139d42bcc -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_34_x86_64.manylinux_2_36_x86_64.whl.
File metadata
- Download URL: dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_34_x86_64.manylinux_2_36_x86_64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.7+, manylinux: glibc 2.34+ x86-64, manylinux: glibc 2.36+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75e998717506c9e98f4cae1d665a0bb34723354407a5334e6846302f02aef50e
|
|
| MD5 |
4b3aa2646b90defdcb93ee7a90fb9a5e
|
|
| BLAKE2b-256 |
c9896fb51232fd58bcdc883436bd47621f5709c0bb17d471e1ed29d0aa7e4870
|
Provenance
The following attestation bundles were made for dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_34_x86_64.manylinux_2_36_x86_64.whl:
Publisher:
publish.yml on ClassesOver/docs_tools
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_34_x86_64.manylinux_2_36_x86_64.whl -
Subject digest:
75e998717506c9e98f4cae1d665a0bb34723354407a5334e6846302f02aef50e - Sigstore transparency entry: 1592213226
- Sigstore integration time:
-
Permalink:
ClassesOver/docs_tools@63c04cd3749d66313bd71f2bd1340c6139d42bcc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ClassesOver
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@63c04cd3749d66313bd71f2bd1340c6139d42bcc -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_24_x86_64.whl.
File metadata
- Download URL: dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_24_x86_64.whl
- Upload date:
- Size: 2.3 MB
- Tags: CPython 3.7+, manylinux: glibc 2.24+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b9907ee7073260216b56e779794a799c568f2b6ac0568f0d9781f648f88019c
|
|
| MD5 |
fd8ef16f5d5e4d8ac44c6c6bfbc11de9
|
|
| BLAKE2b-256 |
f9cc69d07e1cb7a83776c965b302acaa196cbb7f740ec52864353869f2c1020c
|
Provenance
The following attestation bundles were made for dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_24_x86_64.whl:
Publisher:
publish.yml on ClassesOver/docs_tools
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dy_docs_tools-0.1.6-cp37-abi3-manylinux_2_24_x86_64.whl -
Subject digest:
6b9907ee7073260216b56e779794a799c568f2b6ac0568f0d9781f648f88019c - Sigstore transparency entry: 1592213063
- Sigstore integration time:
-
Permalink:
ClassesOver/docs_tools@63c04cd3749d66313bd71f2bd1340c6139d42bcc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ClassesOver
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@63c04cd3749d66313bd71f2bd1340c6139d42bcc -
Trigger Event:
workflow_dispatch
-
Statement type: