Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

23 Commits

Folders and files

Repository files navigation

PDF 转 Word · 云端版

把 PDF 讲义转成 Word,文字、公式、表格、图片都是原生对象,不是截图。 公式转出来是 Word 原生公式,可以直接编辑、可以被搜索。

跟 pdf_to_word(本地版)是同一套出 Word 的管线,区别只在识别那一步 放到了 MinerU 的云端:

本地版 pdf_to_word 云端版(这个)
显卡 必须有 NVIDIA 独显 不用
首次下载 7.4 GB(运行库 2.8 + 模型 4.6) 不用
依赖体积 5.1 GB 95 MB
Office 不用装(2026-09-09 起 XSL 随包) 不用装(转公式那个文件已内置)
额度 无限 每个 mineru 账号每天 1000 页优先解析 + 5000 个文件,可攒多个号叠加
文件 全程不出本机 要上传到 MinerU 的服务器
进度 逐页百分比 + 倒计时 只有「第几份 / 共几份」
并排的配图 原样保留 拆成一张一行

门槛从「N 卡 + 7.4 GB」降到「一个免费 token」。

有一件事云端做不到

本地版用的是 MinerU 的 hybrid-engine 后端,解析强度开到 effort=high。 云端 API 没有这两个开关 —— 它只认五个参数:model_version(只能填 vlm 或 pipeline)、language、is_ocr、enable_formula、enable_table。 往请求里塞 backend=hybrid-engine 或 effort=high 不报错,但服务端直接忽略 (2026-09-09 实测:带不带这两个参数,产出的 word/document.xml 哈希一模一样)。

差别落在并排的配图上。讲义里「甲、乙、丙」三张图排一行、选择题的 A/B/C/D 四张图排一行,这是常态。本地版能把这一行当成一个完整图形区域整体切下来,进了 Word 还是并排的;云端只能一张图算一块,四个选项变成四行,一道题占掉大半页。

同一份 21 页物理讲义,两边实测:

本地版 pdf_to_word 云端版(这个)
图片块 29 40(并排图被拆散)
原生公式 147 192
填空线 40 42

公式和填空线这边更多,图片块那栏是输的 —— 40 比 29 多出来的 11 块不是多认了 11 张图,是原来并排的一张被切成了好几张。

怎么选:讲义、试卷这类版式要紧、又有 N 卡的,用 本地版;没显卡,或者只要把内容 捞成可编辑的文字和公式、不在乎图排成几行,云端版够用而且省事。


给使用者

  1. 双击 PDF转Word.exe
  2. 第一次打开会让你填 MinerU 的 API token —— 去 mineru.net 注册,免费。 每个账号每天 1000 页优先解析、5000 个文件
  3. 把 PDF 拖进窗口,点「开始转换」

不用装 Office。 公式要用微软的 MML2OMML.XSL 转换,这个文件已经跟软件 一起打包好了(runtime/xsl/),装了就能用。WPS 也不用装 —— 转出来的是标准 .docx,用什么打开是你的事。

文件会上传。 解析在 MinerU 的服务器上跑,不能上传的材料请用本地版。

macOS 版

有两个包,按你的机器选:

你的 Mac 下载
M1/M2/M3/M4 等 Apple 芯片 teach-studio-<版本>-mac-arm64.dmg
Intel 芯片(2020 年前的老机器居多) teach-studio-<版本>-mac-x64.dmg

不确定的话点左上角苹果图标 →「关于本机」,看「芯片」那一行。

🔴 第一次打开会被系统拦住,提示"无法打开,因为无法验证开发者"。这不是软件坏了,是因为这个包没有花钱买苹果的开发者签名。放行一次就好,以后正常:

打开「系统设置 → 隐私与安全性」,往下拉,会看到一行"已阻止使用 PDF转Word",点旁边的「仍要打开」。

⚠️ mac 版先别点「检查更新」。 那个功能是给 Windows 版写的,会往程序自己的目录里写文件,而 mac 上那个位置是只读的。要更新请回 GitHub 重新下载。

其余用法跟 Windows 完全一样:填 token、拖文件、点开始。token 和转换历史存在 ~/Library/Application Support/teach-studio/。

多个 token 叠加额度

mineru 一个手机号能注册一个账号、微信还能再注册一个,每个号的每日额度 分开算。软件里可以填 1~10 个 token(左下角「设置」里改):

  • 每转一份之前,挑「今天用得最少」的那个号
  • 某个号额度到顶了(服务端返回 -60018),自动换下一个接着转,不用管
  • 用量显示在设置页里

额度实际是两条,分开算:

限制 性质
文件解析个数 5000 / 天 硬上限,撞了直接拒绝(-60018)
优先解析页数 1000 / 天 软上限,超了不是不给用,是排队降优先级

软件记的是页数——它才是实际会碰到的那条(一份讲义二三十页,五十份 就到 1000 了;5000 个文件正常传不完)。而且因为它是软的,账只用来 「先试哪个号」,从不拿它拦人。

🔴 那个用量数字只算得到通过本软件转掉的。 你自己在 mineru 网页上传的 文件,软件无从知道,所以这个数只会偏小。它只用来决定「先用哪个号」, 真到顶了以服务端的答复为准。

界面

┌ 添加文件  添加文件夹  移除全部        输出:跟原 PDF 放一起  更改 ┐
├──────────────────────────────────────────────────────────────┤
│   文件(共 3 份)                    全选 全不选      页数    │
│ ☑ 解不等式.pdf                                     10 页 移除 │
│ ☑ 电场中力的性质.pdf        第 7、12 页没有文字层   23 页 移除 │
│ ● 太大的.pdf      这份 812 页,超过云端单次上限          移除 │
├──────────────────────────────────────────────────────────────┤
│ ● 2 个 token · 今天已用 340 页 设置 历史 关于  选中 2 份 · 33 页 [开始] │
└──────────────────────────────────────────────────────────────┘

拖进来默认全勾,直接点「开始转换」就行 —— picked 是三态:true / false / 键不存在 = 选中,新加进来的不用挨个勾。点整行任意位置都能 勾(checkbox 设了 pointer-events:none 让点击穿透到行上),一批几十份时 这个差别很大。没勾的那行文件名会调淡,页数固定 52px 右对齐成一列。

没有进度条,也没有倒计时。 云端只告诉你 pending / running / done, 给不出百分比 —— 与其编一个数,不如老实显示「正在转第 2 份,共 5 份」和 云端当前那行状态。

转着的时候还能继续加文件。 新拖进来的进「待办」,摆在主列表下面 单独一块,写着「待办 N 份,这批转完自动接上」。这批转完自动晋升成新一批, 不用你点。中途按了停止的话不会自动开新的 —— 待办并回待转清单,由你决定。

待办不混进主列表是有意的:混了就得在一张表里区分「这批的」和「下批的」, 而那正是「4 个文件下面又冒出一模一样 4 个」那个 bug 的土壤。分开摆, 一眼看清哪些在转、哪些排着。

「停止」只是不再等它。 任务提交出去就在 MinerU 那边跑着,额度照扣。 这跟本地版当场杀进程有本质区别,界面上没有含糊其辞。

转完之后

报告:全都干净时不出现。有转失败的、有扫描页、有公式没转成、或者 删掉过图内文字,才在底下给一个「看报告」。开头写着「没列出来的不代表一定 对」。

出现的时候每一份都列,成功的也列 —— 给「N 页 · 公式 8/8 · 表 2 · 图 3」 和落盘路径。只列有问题的那几份看着清爽,但「没列出来」既可能是干净、也可能 是被漏了,两种情况长得一模一样,用户没法拿它一份份核对 Word。报告不落盘, 所以底栏给了「复制」。

日志:转换中和转完都能点,摊开云端每轮返回的那几行状态。数据一直都在 (后端每轮都返回 lines),原先只在当前那行右边显示最后一句。

历史:任何时候都能点,最多留 200 条,关掉软件也还在。

同一份文件不会重复扣额度

转过的东西按内容指纹存着(_tmp/cache/<指纹>/),指纹是 sha256(PDF 内容) + 提交参数。再转同一份文件时直接用上次的识别结果, 不上传、不扣额度,日志里会说一句「这份转过,不占额度」。

所以:

  • 同一份 PDF 放在两个目录、改了名字 —— 照样各出一份 Word,但只扣一次额度
  • 改了提交参数(比如 is_ocr)或者换了模型 —— 指纹跟着变,自动重新解析, 不需要另写一套「缓存失效」的逻辑
  • 十天没碰过的桶自动清掉

🔴 不能按文件名存。两份不同内容的「讲义.pdf」会落在同一个位置互相 覆盖;你把 PDF 改了重新导出、名字没变,也会拿到旧产物——那种错最难查, 因为界面上一切正常。(这条是本地版的教训,原样搬过来。)

检查更新

设置页最下面。点一下会去 GitHub 查有没有新版本,有的话可以直接更新, 不用自己去下载解压。

这套是从本地版搬来的,它攒了三轮实测的教训:

  • 六条线路并发跑,谁先成功用谁。不是依次试 —— 名单第一条是直连, 网络封了 GitHub 的话要先干等 6 秒超时才轮到第二条,全试一遍最坏 30 秒。 并发之后典型 1.2 秒。
  • 失败了也给线路明细。连不上时用户最想知道的就是「到底哪条不通」, 而那份明细是查更新时顺手就有的,不给等于白扔。
  • 拿不到校验值时报警,但不阻拦。硬拒绝会让更新按钮直接作废。
  • 只推业务代码(0.4 MB),Electron 和 pandoc 那些运行时一个字节不动。
  • 依赖对不上就不让装。判据是 Release 里那份 requires.json 跟本机实际 装的包比对,不是「次版本号变了没有」—— 后者建立在「依赖变了必须进次版本」 这个约定上,约定靠人不出错。

镜像名单的行为会变,而且是双向地变(同一个镜像 09-01 能代理 API、 09-02 变 403、09-03 又反过来),所以候选写一串、并发实测、谁快用谁, 不去维护什么"最优镜像"的静态列表。


给开发者

这个项目是怎么来的

从 pdf_to_word 移植。出 Word 那一整套一行没改——

pipeline/todocx.py    824 行   公式占位符定位、Office XSL、表格边框、
                               图片按英寸还原、主题字体、图内文字过滤
pipeline/tomath.py    259 行   LaTeX → MathML → OMML
pipeline/probe.py     117 行   体检
pipeline/vendor/katex        KaTeX,🔴 忘搬就是「一个公式都没转成」
runtime/xsl/MML2OMML.XSL     微软的 XSL,🔴 忘搬同上

新写的:

pipeline/mineru_api.py   替掉本地版的 extract.py(496 行起子进程)
pipeline/store.py        token(多个)、每天的页数记账、转换历史

接缝只有两个字段:extract 那一步交出 md(markdown 路径)和 auto_dir(含 images/ 的目录),下游完全不知道前面是子进程还是 HTTP。

为什么能直接接上

云端跑的就是同一个 MinerU 引擎,产物格式一模一样(2026-09-08 实测):

本地 云端
markdown <名字>.md full.md
图片 images/ images/
引用 ![](images/x.jpg) ![](images/x.jpg)
行内公式 $...$ $...$
块级公式 $$...$$ $$...$$
表格 HTML HTML

文件名从 <名字>.md 变成 full.md 不影响 —— 找产物是按后缀扫的。

云端 API 的四步

POST /api/v4/file-urls/batch          申请 OSS 上传位 → batch_id
PUT  <签名位>                          🔴 不能带 Content-Type,带了 403
GET  /api/v4/extract-results/batch/…  轮询到 state=done
GET  full_zip_url → 解压              full.md + images/

🔴 HTTP 200 不等于成功。 实测:拿不存在的 task_id 去查,返回的是 HTTP 200 {"code":-60012}。所以每个返回都要看信封里的 code。

没有查额度的接口。 翻遍官方文档也没有 —— 所以「今天还剩多少页」只能 本地自己记(logs/usage.json),而且天生偏小。真正准的信号是错误码 -60018(每日任务数量已达上限),撞上了就换号。

公式为什么不用装 Office

MML2OMML.XSL 是微软随 Office 分发的文件,本地版的做法是去用户机器上 找一份现成的(注册表 + 常见目录)。云端版把它打包进来了 (runtime/xsl/),tomath.find_xsl() 优先读自带那份。

🔴 这是版权文件,内置是作者 2026-09-08 拍板的,前提是这软件只发给 几个熟人。哪天要给外人用或者正经发布,必须换成能自由分发的实现 (texmath / fiduswriter-mathml2omml 那几个开源的)—— 产物放同一个位置, bundled_xsl() 一行都不用改。

开发机上验不了「没装 Office 也能用」 —— 开发机装着 Office,代码随时 可能走到注册表那条路上去,测出来的「能用」是假的。所以测试里特意把 registry_candidates 和 XSL_CANDIDATES 全堵死,只留自带那份,跑通 端到端才算数(Test自带的XSL)。

从本地版带过来的教训(一开始就避开)

那边 2026-09-08 三个 agent 查出三十余条,这份在同样的位置做了不同的选择:

本地版栽的 这里怎么做的
HTTP.get 不检查 r.ok,404 当正常数据,为它写的 catch 是死代码 httpGet 第一版就检查状态码,且有测试钉着
任务表淘汰会把 running 的挤掉,互斥失效 只淘汰终态,有测试
轮询返回浅拷贝,序列化在锁外,日志被工作线程截断 在锁内就拷好,有测试
同一条忙碌判据抄四处,一处漏了新增项 只有 _busy() 一个函数
五处「功能写好了没人调」 契约测试扫后端每条路由有没有前端调用点
报告页返回按钮在内容后面,被挤出屏幕 按钮在前,测试断言 按钮位置 < 内容位置

跑测试

.venv\Scripts\python.exe -m unittest discover -s tests -q   # 后端 115 条
node tests\front_check.js                                   # 前端 85 条

前端那些是真渲染:在 Node 里用假 DOM 把 pages.js 跑起来,断言它吐出来 的 HTML。不是「函数存在就算过」。

目录

pipeline/   业务:体检、云端解析、出 Word、落盘
server/     本地 HTTP(绑 127.0.0.1,端口随机)
app/        Electron 外壳 + 前端(无框架,全局 state + 整页重绘)
runtime/    打包进来的 pandoc 和 MML2OMML.XSL
tests/      后端 115 条 + 前端 85 条

token 和账本存哪

都在 logs/ 下,而 logs/ 在 .gitignore 里:

logs/token.json   {"slots": 2, "tokens": ["sk-...", "sk-..."]}
logs/usage.json   {"2026-09-08": {"<token 的 sha256 前 12 位>": 340}}

token 不加密 —— 加密要有密钥,密钥又得存在同一台机器上。真正的边界是 文件系统权限。但显示一律打码(store.masked),日志、历史、报错里一个 字符都不许出现,有测试盯着。

账本存指纹不存原文 —— 它跟 token.json 躺在同一个目录,凭据只该有一个 出处。指纹不可逆,换了 token 指纹跟着变,旧账自然作废。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages