dsh-convert-core 0.1.0-alpha.6 → 0.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -16
- package/bin/media-to-md.mjs +5 -5
- package/bin/tita-convert.mjs +1 -1
- package/docs/media-to-md.md +41 -16
- package/lib/adapters/iddoc.d.ts +3 -1
- package/lib/adapters/iddoc.d.ts.map +1 -1
- package/lib/adapters/iddoc.js +18 -6
- package/lib/adapters/iddoc.js.map +1 -1
- package/lib/adapters/index.d.ts +12 -5
- package/lib/adapters/index.d.ts.map +1 -1
- package/lib/adapters/index.js +14 -10
- package/lib/adapters/index.js.map +1 -1
- package/lib/adapters/scanned.d.ts +14 -6
- package/lib/adapters/scanned.d.ts.map +1 -1
- package/lib/adapters/scanned.js +30 -12
- package/lib/adapters/scanned.js.map +1 -1
- package/lib/adapters/types.d.ts +1 -1
- package/lib/adapters/types.d.ts.map +1 -1
- package/lib/adapters/web.d.ts +9 -12
- package/lib/adapters/web.d.ts.map +1 -1
- package/lib/adapters/web.js +30 -53
- package/lib/adapters/web.js.map +1 -1
- package/lib/contract.d.ts +55 -4
- package/lib/contract.d.ts.map +1 -1
- package/lib/contract.js +9 -2
- package/lib/contract.js.map +1 -1
- package/lib/http.d.ts.map +1 -1
- package/lib/http.js +85 -13
- package/lib/http.js.map +1 -1
- package/lib/media/convert.d.ts.map +1 -1
- package/lib/media/convert.js +68 -19
- package/lib/media/convert.js.map +1 -1
- package/lib/media/index.d.ts +2 -2
- package/lib/media/index.d.ts.map +1 -1
- package/lib/media/index.js +1 -1
- package/lib/media/index.js.map +1 -1
- package/lib/media/markdown.d.ts +20 -2
- package/lib/media/markdown.d.ts.map +1 -1
- package/lib/media/markdown.js +31 -5
- package/lib/media/markdown.js.map +1 -1
- package/lib/media/models.d.ts +67 -2
- package/lib/media/models.d.ts.map +1 -1
- package/lib/media/models.js +221 -13
- package/lib/media/models.js.map +1 -1
- package/lib/media/scanned.d.ts +35 -7
- package/lib/media/scanned.d.ts.map +1 -1
- package/lib/media/scanned.js +81 -38
- package/lib/media/scanned.js.map +1 -1
- package/lib/media/types.d.ts +56 -11
- package/lib/media/types.d.ts.map +1 -1
- package/lib/media/types.js.map +1 -1
- package/lib/service.d.ts +140 -8
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +294 -30
- package/lib/service.js.map +1 -1
- package/lib/web/convert.d.ts +11 -14
- package/lib/web/convert.d.ts.map +1 -1
- package/lib/web/convert.js +26 -125
- package/lib/web/convert.js.map +1 -1
- package/lib/web/fetch.js +1 -1
- package/lib/web/fetch.js.map +1 -1
- package/lib/web/index.d.ts +3 -1
- package/lib/web/index.d.ts.map +1 -1
- package/lib/web/index.js +3 -1
- package/lib/web/index.js.map +1 -1
- package/lib/web/types.d.ts +10 -22
- package/lib/web/types.d.ts.map +1 -1
- package/lib/web/types.js +3 -1
- package/lib/web/types.js.map +1 -1
- package/lib/web/url.d.ts +6 -0
- package/lib/web/url.d.ts.map +1 -1
- package/lib/web/url.js +7 -1
- package/lib/web/url.js.map +1 -1
- package/package.json +10 -6
- package/scripts/__pycache__/asr.cpython-312.pyc +0 -0
- package/scripts/asr.py +157 -10
- package/scripts/pdf_pages.py +102 -12
- package/src/adapters/iddoc.ts +18 -6
- package/src/adapters/index.ts +14 -9
- package/src/adapters/scanned.ts +30 -12
- package/src/adapters/types.ts +1 -1
- package/src/adapters/web.ts +32 -53
- package/src/contract.ts +56 -4
- package/src/http.ts +82 -11
- package/src/media/convert.ts +67 -21
- package/src/media/index.ts +17 -1
- package/src/media/markdown.ts +34 -6
- package/src/media/models.ts +229 -14
- package/src/media/scanned.ts +107 -40
- package/src/media/types.ts +58 -11
- package/src/service.ts +343 -31
- package/src/web/convert.ts +26 -138
- package/src/web/fetch.ts +2 -2
- package/src/web/index.ts +3 -1
- package/src/web/types.ts +10 -22
- package/src/web/url.ts +7 -1
- package/web/app.js +50 -389
- package/web/index.html +14 -212
- package/web/styles.css +248 -235
- package/scripts/vendor.mjs +0 -76
- package/web/vendor/icons.js +0 -25
- package/web/vendor/vue.global.prod.js +0 -14
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# dsh-convert-core
|
|
2
2
|
|
|
3
3
|
转换**内核**:把 Office / OpenDocument / RTF / EPUB / CSV / 文本层 PDF、音视频与图片、
|
|
4
|
-
|
|
5
|
-
自带 HTTP API 和一个
|
|
4
|
+
混合层 PDF、**另存下来的网页(含微信公众号文章)** 转成 **知识就绪 Markdown + 本地图片**,
|
|
5
|
+
自带 HTTP API 和一个 React(`@tita-data-agent/ui`)可视化控制台。
|
|
6
6
|
它可以**独立运行**,不依赖 DSH。
|
|
7
7
|
|
|
8
|
+
**单一职责:只做转换,不联网取内容。** 输入是磁盘上的一份文件(或一段内联文本),输出是
|
|
9
|
+
Markdown。需要浏览器才能读的页面(风控、验证码、JS 渲染、登录态)由宿主用自己的浏览器读下来
|
|
10
|
+
另存为 `.html`,再按文件交给内核 —— 内核**没有**「粘一个网址让它去抓」这条路。
|
|
11
|
+
|
|
8
12
|
```bash
|
|
9
13
|
npm install dsh-convert-core
|
|
10
14
|
npx tita-convert --port 8787
|
|
@@ -59,17 +63,23 @@ const handler = createRequestHandler({ service, basePath: '/convert' })
|
|
|
59
63
|
## 产物契约
|
|
60
64
|
|
|
61
65
|
```
|
|
62
|
-
|
|
66
|
+
<outRoot>/<YYYY-MM-DD>/<中文名>/ # 日期层由 bucketByDay 打开;叶子名见 deliveryDirName()
|
|
63
67
|
index.md front matter + 正文,图片引用为 assets/img-<sha1_12>.<ext>
|
|
64
68
|
assets/ 内容寻址的本地图片
|
|
65
69
|
source/ 原始输入(0048):抓下来的 HTML、上传的那份文件
|
|
66
70
|
manifest.json 来源、引擎与格式、warnings、content_hash、ingest 负载
|
|
67
71
|
```
|
|
68
72
|
|
|
73
|
+
交付目录是**用户会打开**的东西,所以叶子名是文档的中文名(`output/2026-10-02/月度销售汇总/`),
|
|
74
|
+
不是 `conv_<时间戳>_<随机>`:名字取标题,没有标题就用文件名(去掉扩展名),最多 25 个字,
|
|
75
|
+
同一天同名让路成 `-2`、`-3`。让路结果记在任务的 `deliveryName` 上,所以重试与重启之后算出来的
|
|
76
|
+
都是同一个目录;那份记录早于这个字段时回退到 task id。**工作目录不跟着改名**(仍按 task id):
|
|
77
|
+
工地只要唯一,成品的位置要稳定。
|
|
78
|
+
|
|
69
79
|
**`source/` 是底稿,不是资产**(0048)。抽取是有损的(正文外的元素、内嵌音视频都不在
|
|
70
80
|
产物里),而工作目录在交付成功那一刻就被删掉 —— 不留底稿,事后想换个抽取规则重来一遍就
|
|
71
|
-
没有素材了。网页那档由适配器交出**真正被解析的那份 HTML
|
|
72
|
-
|
|
81
|
+
没有素材了。网页那档由适配器交出**真正被解析的那份 HTML**(就是宿主交进来的那份 `.html`);
|
|
82
|
+
其他档由内核把暂存输入复制过来。`manifest.original` 记它的
|
|
73
83
|
`rel` / `bytes` / `sha256`,`manifest.ingest.original` 让知识层也能记住它。内联文本没有独立
|
|
74
84
|
底稿(`index.md` 就是它),所以不写这个键,而不是造一个空的。
|
|
75
85
|
|
|
@@ -87,14 +97,12 @@ source_type_source / source_type_candidates` 由内核如实填写。
|
|
|
87
97
|
## 范围
|
|
88
98
|
|
|
89
99
|
文件类:14 种文档格式(含文本层 PDF)、本地音视频与图片、扫描件轻量 OCR、另存的页面(`.html/.htm`)。
|
|
90
|
-
**网页(`kind=url` 链接)已落地**:静态抓取 + 正文抽取,公众号文章有专用路径。
|
|
91
100
|
还没做:内核内的浏览器档(Playwright / CDP)、在线视频字幕/转写、版面/表格/公式级的高质量扫描档
|
|
92
101
|
(docling / MinerU)。
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
地址读回来,产物与直抓完全一致。
|
|
103
|
+
**抓取不在本包范围**(原 `kind=url` 已删除)。需要真浏览器才能读的页面(公众号重复访问会跳
|
|
104
|
+
验证码页这类风控墙)本来就不是静态抓取能解决的,而宿主手里有真浏览器 —— 让它读下来另存为
|
|
105
|
+
`.html`,再按文件提交。本档会从 `og:url` / `canonical` 把原始地址读回来,产物与直抓一致。
|
|
98
106
|
|
|
99
107
|
Agent 侧的同一件事已经写成 skill:**`browser-page-to-markdown`** —— 用 Kimi WebBridge 驱动
|
|
100
108
|
用户的真实浏览器读一次页面、把渲染后的 HTML 落到本地,再交给本档(`kind=file` 的 `.html`)。
|
|
@@ -112,7 +120,7 @@ Agent 侧的同一件事已经写成 skill:**`browser-page-to-markdown`** —
|
|
|
112
120
|
| 输入 | 适配器 | 说明 |
|
|
113
121
|
| --- | --- | --- |
|
|
114
122
|
| 14 种文档格式(含文本层 PDF) | `anydoc` | 见上;内嵌图片落地到 `assets/` |
|
|
115
|
-
|
|
|
123
|
+
| 另存下来的网页 / 微信公众号文章(`kind=file` 的 `.html/.htm`) | `web` | 能力来自内核自带的网页管线 `src/web/`:读入一份**已在手上**的 HTML → 正文抽取(公众号走 `#js_content`,其他站点走 Readability)→ 图片按内容 sha1 落地 `assets/` → turndown 出 GFM Markdown。**不抓页面、不跟进链接**(唯一的网络请求是正文图片);正文图片引用一律改成 `assets/…`,远程地址不会留在产物里。原始地址从页面自带的 `og:url` / `canonical` 读回来(相对地址、`source_url`、幂等身份都靠它) |
|
|
116
124
|
| 音视频(`.mp4 .mov .mkv .webm …`、`.mp3 .m4a .wav …`)与图片(`.png .jpg .webp …`) | `iddoc` | 能力来自内核自带的本地媒体管线 [`src/media/`](docs/media-to-md.md):**字幕优先**,无字幕时才本地转写(默认**不**下载模型,`IDDOC_ASR=1` 才允许);图片默认只收录不做 OCR;视频关键帧(均匀采样 + 场景变化 + 内容去重)落地到 `assets/` |
|
|
117
125
|
| 扫描件 / 纯图片页 PDF | `scanned` | 同一包的 `convertScannedDocument()`:`pypdfium2` 逐页渲染 + 本地 `RapidOCR` 识别,`page-0001.png …` 落地为 assets。默认作为 `anydoc` 报 `needsOcr` 时的**自动兜底**;`SCANNED_OCR=1` 是「跳过 anydoc 直接走 OCR」的显式出口。不做版面还原 |
|
|
118
126
|
| `.md .markdown .txt .json .tsv .yml .log .adoc .rst .org .tex` | `text` | 直通 + 契约归一化(重写 front matter)|
|
|
@@ -159,12 +167,22 @@ curl -X POST "localhost:8787/api/tasks?kind=file" -F "file=@spec.docx"
|
|
|
159
167
|
|
|
160
168
|
## 控制台
|
|
161
169
|
|
|
162
|
-
`web/index.html` + `web/app.js
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
`web/index.html` + `web/app.js` + `web/styles.css`,由 `build-console.mjs` 用 esbuild
|
|
171
|
+
从 `console/` 打出来。界面是 React + `@tita-data-agent/ui`(仓库共享的 shadcn 底座),
|
|
172
|
+
样式表直接拷 `packages/ui` 编译出的那一份 —— 控制台和三个插件穿同一套视觉,不再各造一套。
|
|
173
|
+
|
|
174
|
+
**运行时仍然零构建**:内核只负责把 `web/` 下这三个静态文件发出去,`npx tita-convert`
|
|
175
|
+
不需要打包器;打包器只在**构建期**存在,产物自包含。代价是改控制台的类名之后要重编
|
|
176
|
+
`packages/ui`(`@source` 清单在那里),这也是三个插件一直以来的做法。
|
|
177
|
+
|
|
178
|
+
界面结构是**两个视图**(右上角切换):
|
|
165
179
|
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
- **转换** —— 一个输入框收文件(拖进来,或点「浏览文件」)、
|
|
181
|
+
依赖项清单(ffmpeg / anydoc / uvx / readability / whisper,名字 + 版本 + 它负责什么)、
|
|
182
|
+
支持的格式(按谁负责它分组,共 72 种)。
|
|
183
|
+
- **任务列表** —— 四列的表:标题(第二行是它出了什么事)| 状态 | 时间 | 操作。
|
|
184
|
+
失败的出路是行内一枚按钮,完整建议收进展开的「说明」活页。停下来的一行可以删除
|
|
185
|
+
(记录 + 交付目录一起删,删前确认)。
|
|
168
186
|
|
|
169
187
|
## 设计取舍
|
|
170
188
|
|
package/bin/media-to-md.mjs
CHANGED
|
@@ -51,16 +51,16 @@ function usage() {
|
|
|
51
51
|
' --mode <档位> auto | subtitle-only | transcribe | frames-only(默认 auto)',
|
|
52
52
|
' --model <name|path> whisper 模型(默认 small;中文建议 large-v3-turbo;也可是本地模型目录)',
|
|
53
53
|
' --lang <code> 语言(默认自动检测;中文建议 zh)',
|
|
54
|
-
' --allow-model-download 允许在无字幕时下载转写模型(
|
|
54
|
+
' --allow-model-download 允许在无字幕时下载转写模型(默认不允许:auto 档就不出逐字稿,transcribe 档直接报错并告知体积)',
|
|
55
55
|
' --samples <n> 均匀采样帧数(默认 12)',
|
|
56
56
|
' --max-frames <n> 关键帧上限(默认 40)',
|
|
57
57
|
' --scene <0..1> 场景切换阈值(默认 0.3)',
|
|
58
58
|
' --ocr-frames 对关键帧做画面文字 OCR(默认关闭)',
|
|
59
59
|
' --image-ocr 对图片输入做 OCR(默认关闭,图片直接收录)',
|
|
60
|
-
' --scanned-pdf
|
|
61
|
-
' --dpi <n>
|
|
62
|
-
' --max-pages <n>
|
|
63
|
-
' --no-page-images
|
|
60
|
+
' --scanned-pdf PDF 逐页混合档:有文本层的页用文本层,缺文本层的页走本地 OCR(默认关闭)',
|
|
61
|
+
' --dpi <n> 需要 OCR 的页的渲染 DPI(默认 150)',
|
|
62
|
+
' --max-pages <n> 最多处理页数(默认 50)',
|
|
63
|
+
' --no-page-images 不落地页图,只保留文字',
|
|
64
64
|
' --title <text> 产物标题(默认文件名)',
|
|
65
65
|
' --uvx <path> / --python <v> 自定义 uvx 与 Python 版本',
|
|
66
66
|
' --keep-scratch 保留中间目录(排障)',
|
package/bin/tita-convert.mjs
CHANGED
package/docs/media-to-md.md
CHANGED
|
@@ -21,7 +21,16 @@
|
|
|
21
21
|
| 画面文字 / 扫描页文字 → 文字 | `RapidOCR`(ONNX) | **不是 LLM**。OCR 小模型,几十 MB |
|
|
22
22
|
| 拼装 Markdown | 本包模板 | 纯规则 |
|
|
23
23
|
|
|
24
|
-
模型只在首次使用时下载一次,之后完全离线。**是否允许下载由调用方显式决定**(`asr.allowModelDownload`,默认 `false`)
|
|
24
|
+
模型只在首次使用时下载一次,之后完全离线。**是否允许下载由调用方显式决定**(`asr.allowModelDownload`,默认 `false`)。
|
|
25
|
+
|
|
26
|
+
「允许下载」和「可以使用」是**两件事**,判据也分开:
|
|
27
|
+
|
|
28
|
+
- 本地已经有模型(调用方给的目录,或 huggingface_hub 缓存里的快照)→ **直接用**,不需要那个开关。
|
|
29
|
+
原来的写法把两件事合成一个布尔值,于是模型已经躺在缓存里的机器照样被拒。
|
|
30
|
+
- 没字幕、本地也没有模型、又不允许下载:
|
|
31
|
+
- `mode: 'auto'`(默认)—— **不转写,也不失败**。关键帧、时长、编码信息照常交付,
|
|
32
|
+
缺的那一块写成 `TRANSCRIPT_UNAVAILABLE` warning 与正文里的一句说明。
|
|
33
|
+
- `mode: 'transcribe'` —— 明确要逐字稿,拿不到就抛 `ASR_MODEL_REQUIRED`,并写清"要下多大"。
|
|
25
34
|
|
|
26
35
|
## 安装与构建
|
|
27
36
|
|
|
@@ -56,33 +65,43 @@ const result = await convertMedia({
|
|
|
56
65
|
|
|
57
66
|
| mode | 字幕 | 转写 | 说明 |
|
|
58
67
|
| --- | --- | --- | --- |
|
|
59
|
-
| `auto`(默认) | 优先 |
|
|
68
|
+
| `auto`(默认) | 优先 | 本地有模型就转;允许下载才下;两者都不成立就**只交付关键帧并写明原因**,不整单失败 | 有字幕就绝不碰模型 |
|
|
60
69
|
| `subtitle-only` | 只用 | 永不 | 没字幕就是"没有逐字稿",不报错 |
|
|
61
|
-
| `transcribe` | 跳过 | 强制 |
|
|
70
|
+
| `transcribe` | 跳过 | 强制 | 拿不到逐字稿就抛 `ASR_MODEL_REQUIRED` |
|
|
62
71
|
| `frames-only` | 跳过 | 跳过 | 只要关键帧 |
|
|
63
72
|
|
|
64
|
-
##
|
|
73
|
+
## PDF 逐页混合档:`convertScannedDocument()`
|
|
65
74
|
|
|
66
|
-
|
|
75
|
+
**逐页**判断:有文本层的页取文本层(不渲染、不 OCR),缺文本层的页才渲染页图走本地 OCR。
|
|
76
|
+
|
|
77
|
+
为什么是逐页而不是整份:混合 PDF(扫描封面 + 电子正文)在现实里很常见,而按整份判断
|
|
78
|
+
两种方向都会出错 —— anydoc 那样"有一页是图片就整份放弃"会把好文字的页一起丢掉;
|
|
79
|
+
反过来"见到图片页就整份 OCR"会把无损的文字换成有识别误差的版本。
|
|
67
80
|
|
|
68
81
|
```ts
|
|
69
82
|
import { convertScannedDocument } from 'dsh-convert-core/media'
|
|
70
83
|
|
|
71
84
|
const result = await convertScannedDocument({
|
|
72
|
-
file: '/abs/
|
|
73
|
-
dpi: 150,
|
|
74
|
-
maxPages: 50,
|
|
75
|
-
|
|
85
|
+
file: '/abs/report.pdf',
|
|
86
|
+
dpi: 150,
|
|
87
|
+
maxPages: 50,
|
|
88
|
+
// 一页的非空白字符数低于它就当作没有文本层(默认 16)。
|
|
89
|
+
// 调高会更激进地走 OCR,适合"文本层是乱码的伪文本 PDF"。
|
|
90
|
+
ocrThreshold: 16,
|
|
76
91
|
})
|
|
77
|
-
// result.pages
|
|
92
|
+
// result.pages[i].source 是 'text' | 'ocr' —— 产物里也标着,读者据此知道哪一页可逐字引用。
|
|
78
93
|
```
|
|
79
94
|
|
|
95
|
+
原来的 `convertScannedDocument()` 是"整份 OCR"档:纯图片页 PDF 走它没问题,但混合 PDF
|
|
96
|
+
会被整份 OCR 一遍。现在它对两种输入都对。
|
|
97
|
+
|
|
80
98
|
**它不做/不承诺什么**(这是刻意的):
|
|
81
99
|
|
|
82
100
|
- **不做版面还原**:输出是"第 N 页 + 该页图片 + 该页文字",顺序 = 页序;多栏、表格、公式会乱;
|
|
83
|
-
- **不引入 docling / PaddleOCR 框架 / MinerU**:那是 `
|
|
84
|
-
- **不联网**:只有两个本地组件 —— `pypdfium2`(渲染)与 `RapidOCR`(识别);
|
|
85
|
-
-
|
|
101
|
+
- **不引入 docling / PaddleOCR 框架 / MinerU**:那是 `docs/issue/0017` 的"可选高质量档"(几 GB ~ 20 GB 依赖);
|
|
102
|
+
- **不联网**:只有两个本地组件 —— `pypdfium2`(取文本 + 渲染)与 `RapidOCR`(识别);
|
|
103
|
+
- **不判断该不该用**:默认路径先让文档引擎试文本层,它报 `needsOcr` 时才换到本档
|
|
104
|
+
(内核自动换,见 `resolveFallbackAdapter`);`SCANNED_OCR=1` 可跳过那一步直接走本档。
|
|
86
105
|
|
|
87
106
|
## CLI
|
|
88
107
|
|
|
@@ -91,12 +110,12 @@ const result = await convertScannedDocument({
|
|
|
91
110
|
node packages/dsh-convert-core/bin/media-to-md.mjs clip.mp4 --lang zh --model large-v3-turbo --allow-model-download
|
|
92
111
|
# → clip.md-out/{index.md, assets/, transcript.srt}
|
|
93
112
|
|
|
94
|
-
#
|
|
113
|
+
# PDF 逐页混合(有文本层用文本层,只有图片页走 OCR;无需任何系统依赖)
|
|
95
114
|
node packages/dsh-convert-core/bin/media-to-md.mjs scan.pdf --scanned-pdf --dpi 200
|
|
96
115
|
# → scan.md-out/{index.md, assets/page-0001.png …}
|
|
97
116
|
```
|
|
98
117
|
|
|
99
|
-
|
|
118
|
+
PDF 档相关参数:`--scanned-pdf`(跳过文本层尝试,直接走本档)、`--dpi <n>`、`--max-pages <n>`、`--no-page-images`。
|
|
100
119
|
|
|
101
120
|
## 设计取舍
|
|
102
121
|
|
|
@@ -120,6 +139,12 @@ node packages/dsh-convert-core/bin/media-to-md.mjs scan.pdf --scanned-pdf --dpi
|
|
|
120
139
|
|
|
121
140
|
- **`Could not create temporary file` / uv 缓存不可写**:把缓存重定向到可写目录:
|
|
122
141
|
`UV_CACHE_DIR`、`UV_PYTHON_INSTALL_DIR`、`UV_TOOL_DIR`、`UV_TOOL_BIN_DIR`、`XDG_CACHE_HOME`。
|
|
142
|
+
- **`TypeError: open() got an unexpected keyword argument 'metadata_errors'`**:
|
|
143
|
+
`faster-whisper` 声明 `av>=11` 却**没有上界**,而 `av.open()` 的签名改过 —— av 19 删掉了
|
|
144
|
+
`metadata_errors`,**av 19 起就拒收这个参数**(实测 13~18 收得下)。转写时 `asr.py` 把
|
|
145
|
+
16 kHz 单声道 WAV 自己解成波形再交给模型(`transcribe()` 收到 ndarray 就跳过 `decode_audio`),
|
|
146
|
+
所以正常路径完全不碰 PyAV;只有输入不是这种 WAV、退回"传路径"时才可能撞上。
|
|
147
|
+
临时解法:`uvx --python 3.12 --with faster-whisper --with "av<19" python scripts/asr.py <音频>`。
|
|
123
148
|
- **模型下载失败(SSL / Server disconnected)**:Python 侧走 HuggingFace 下载不稳定时,用 curl 预先下好模型目录再把 `asr.model` 指向它:
|
|
124
149
|
|
|
125
150
|
```bash
|
|
@@ -140,4 +165,4 @@ node packages/dsh-convert-core/bin/media-to-md.mjs scan.pdf --scanned-pdf --dpi
|
|
|
140
165
|
|
|
141
166
|
- 长音频未做 VAD 分片与取消后的分片保留(子进程会随 `AbortSignal` 结束)。
|
|
142
167
|
- 说话人分离未接。
|
|
143
|
-
- 网页链接与扫描件 OCR 不在本包范围(见 `
|
|
168
|
+
- 网页链接与扫描件 OCR 不在本包范围(见 `docs/issue/0017` 的 `web.ts` / `docling.ts`)。
|
package/lib/adapters/iddoc.d.ts
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 与文档档(anydoc)一样:引擎独立于 adapter,内核只管契约。
|
|
12
12
|
*
|
|
13
|
-
* 环境变量(都是**显式**开关,默认不下载模型、不对图片做 OCR)
|
|
13
|
+
* 环境变量(都是**显式**开关,默认不下载模型、不对图片做 OCR)。
|
|
14
|
+
* `request.media` 里同名的项**优先于**这些环境变量 —— 环境变量是这台机器的默认值,
|
|
15
|
+
* `media` 是这一次转换的选择(面板上的开关):
|
|
14
16
|
* IDDOC_ASR=1 允许在无字幕时下载模型并本地转写
|
|
15
17
|
* IDDOC_ASR_MODEL whisper 模型(默认 small;中文建议 large-v3-turbo)
|
|
16
18
|
* IDDOC_ASR_LANG 语言代码(默认自动检测;中文建议 zh)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iddoc.d.ts","sourceRoot":"","sources":["../../src/adapters/iddoc.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"iddoc.d.ts","sourceRoot":"","sources":["../../src/adapters/iddoc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAYH,OAAO,EAAgB,KAAK,OAAO,EAAyF,MAAM,YAAY,CAAA;AAE9I,eAAO,MAAM,sBAAsB,UAElC,CAAA;AACD,eAAO,MAAM,sBAAsB,UAElC,CAAA;AACD,eAAO,MAAM,sBAAsB,UAAmB,CAAA;AACtD,eAAO,MAAM,gBAAgB,UAAoF,CAAA;AAmIjH,eAAO,MAAM,YAAY,EAAE,OAQ1B,CAAA"}
|
package/lib/adapters/iddoc.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 与文档档(anydoc)一样:引擎独立于 adapter,内核只管契约。
|
|
12
12
|
*
|
|
13
|
-
* 环境变量(都是**显式**开关,默认不下载模型、不对图片做 OCR)
|
|
13
|
+
* 环境变量(都是**显式**开关,默认不下载模型、不对图片做 OCR)。
|
|
14
|
+
* `request.media` 里同名的项**优先于**这些环境变量 —— 环境变量是这台机器的默认值,
|
|
15
|
+
* `media` 是这一次转换的选择(面板上的开关):
|
|
14
16
|
* IDDOC_ASR=1 允许在无字幕时下载模型并本地转写
|
|
15
17
|
* IDDOC_ASR_MODEL whisper 模型(默认 small;中文建议 large-v3-turbo)
|
|
16
18
|
* IDDOC_ASR_LANG 语言代码(默认自动检测;中文建议 zh)
|
|
@@ -63,8 +65,16 @@ async function probeAvailability() {
|
|
|
63
65
|
return { available: true, versions: reported };
|
|
64
66
|
}
|
|
65
67
|
function toOptions(context, file) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
/*
|
|
69
|
+
* 优先级:**本次请求 > 环境变量 > 内置默认**。
|
|
70
|
+
*
|
|
71
|
+
* 环境变量是"这台机器/这个部署"的默认值(运维设一次),请求里的是"这一次转换"的
|
|
72
|
+
* 选择(用户在面板上点的那个开关)。两者都需要:没有前者,每个调用方都得自己写一遍;
|
|
73
|
+
* 没有后者,界面上就无处安放「允许下载模型」这个决定 —— 而那正是原来卡住用户的地方。
|
|
74
|
+
*/
|
|
75
|
+
const media = context.request.media ?? {};
|
|
76
|
+
const model = media.asrModel?.trim() || (process.env.IDDOC_ASR_MODEL ?? '').trim() || 'small';
|
|
77
|
+
const language = media.asrLanguage?.trim() || (process.env.IDDOC_ASR_LANG ?? '').trim();
|
|
68
78
|
const frames = {
|
|
69
79
|
...(envInt('IDDOC_SAMPLES') ? { samples: envInt('IDDOC_SAMPLES') } : {}),
|
|
70
80
|
...(envInt('IDDOC_MAX_FRAMES') ? { maxFrames: envInt('IDDOC_MAX_FRAMES') } : {}),
|
|
@@ -74,6 +84,8 @@ function toOptions(context, file) {
|
|
|
74
84
|
...(process.env.IDDOC_UVX?.trim() ? { uvxPath: process.env.IDDOC_UVX.trim() } : {}),
|
|
75
85
|
...(process.env.IDDOC_PYTHON?.trim() ? { pythonVersion: process.env.IDDOC_PYTHON.trim() } : {}),
|
|
76
86
|
};
|
|
87
|
+
const ocrFrames = media.ocrFrames ?? envFlag('IDDOC_OCR_FRAMES');
|
|
88
|
+
const imageOcr = media.imageOcr ?? envFlag('IDDOC_IMAGE_OCR');
|
|
77
89
|
return {
|
|
78
90
|
file,
|
|
79
91
|
...(context.request.title?.trim() ? { title: context.request.title.trim() } : {}),
|
|
@@ -81,12 +93,12 @@ function toOptions(context, file) {
|
|
|
81
93
|
asr: {
|
|
82
94
|
model,
|
|
83
95
|
...(language ? { language } : {}),
|
|
84
|
-
allowModelDownload: envFlag('IDDOC_ASR'),
|
|
96
|
+
allowModelDownload: media.allowModelDownload ?? envFlag('IDDOC_ASR'),
|
|
85
97
|
},
|
|
86
98
|
...(Object.keys(frames).length > 0 ? { frames } : {}),
|
|
87
99
|
...(Object.keys(tools).length > 0 ? { tools } : {}),
|
|
88
|
-
...(
|
|
89
|
-
...(
|
|
100
|
+
...(imageOcr ? { imageOcr: true } : {}),
|
|
101
|
+
...(ocrFrames ? { ocrFrames: true } : {}),
|
|
90
102
|
...(context.signal ? { signal: context.signal } : {}),
|
|
91
103
|
onProgress: progress => context.report(progress.progress, progress.stage, progress.message),
|
|
92
104
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iddoc.js","sourceRoot":"","sources":["../../src/adapters/iddoc.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"iddoc.js","sourceRoot":"","sources":["../../src/adapters/iddoc.ts"],"names":[],"mappings":"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"}
|
package/lib/adapters/index.d.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* pass-through. Unknown extensions are only treated as text when the bytes are
|
|
7
7
|
* actually textual — guessing there is how "converted successfully" becomes a lie.
|
|
8
8
|
*
|
|
9
|
-
* `.pdf` 默认仍走 anydoc(
|
|
10
|
-
* {@link resolveFallbackAdapter} 当场改走 `scanned`(见那里的理由)
|
|
9
|
+
* `.pdf` 默认仍走 anydoc(整份都是文本层时质量最好、零依赖);它报 `needsOcr` 时由
|
|
10
|
+
* {@link resolveFallbackAdapter} 当场改走 `scanned`(见那里的理由)。改走之后是**逐页**
|
|
11
|
+
* 判断:有文本层的页仍然用文本层,只有图片页才渲染 + OCR —— 所以"32 页里有 1 页是图片"
|
|
12
|
+
* 不会把另外 31 页的无损文字换成 OCR 结果。
|
|
11
13
|
*/
|
|
12
14
|
import type { ConvertRequest } from '../contract.js';
|
|
13
15
|
import { type Adapter, type AdapterAvailability } from './types.js';
|
|
@@ -33,7 +35,12 @@ export interface FallbackDecision {
|
|
|
33
35
|
/**
|
|
34
36
|
* 失败了要不要**当场换一个档再试一次**。
|
|
35
37
|
*
|
|
36
|
-
* 今天只有一条:anydoc 报 `needsOcr` 且输入是 `.pdf` → 改走 `scanned
|
|
38
|
+
* 今天只有一条:anydoc 报 `needsOcr` 且输入是 `.pdf` → 改走 `scanned`(PDF 逐页混合档)。
|
|
39
|
+
*
|
|
40
|
+
* **注意 anydoc 的判据是"整份文档"**:它只要发现**任意一页**需要 OCR 就整份放弃,
|
|
41
|
+
* 报出来的那句 `page 1 of 32 needs OCR` 说的其实是"第 1 页就是图片,我不做了",
|
|
42
|
+
* 而不是"32 页里只有第 1 页有问题"。所以换档不是"退而求其次",而是把 31 页本来
|
|
43
|
+
* 可以无损读出的文本层救回来 —— 换过去的档逐页判断,有文本层的页仍然走文本层。
|
|
37
44
|
*
|
|
38
45
|
* 为什么是自动的、而不是像 0017 原来写的那样要求用户显式指定 adapter:
|
|
39
46
|
*
|
|
@@ -44,10 +51,10 @@ export interface FallbackDecision {
|
|
|
44
51
|
* 2. **它不改变「不静默降级」这条底线。** 换档会写进任务消息(面板上看得到)与
|
|
45
52
|
* `manifest.warnings`(`ADAPTER_FALLBACK`),并且产物里带 `SCANNED_TIER` 警告说明
|
|
46
53
|
* 这一档不做版面还原。用户看到的是「换了什么、为什么换、代价是什么」,不是「假装成功」。
|
|
47
|
-
* 3. **代价可控。**
|
|
54
|
+
* 3. **代价可控。** 这一档全程本地(pypdfium2 + RapidOCR,不出网),只在 uvx 存在时才走;
|
|
48
55
|
* uvx 不在就原样抛出 anydoc 的错误。
|
|
49
56
|
*
|
|
50
|
-
* 仍然保留 `SCANNED_OCR=1` 那条显式出口:它管的是「跳过 anydoc
|
|
57
|
+
* 仍然保留 `SCANNED_OCR=1` 那条显式出口:它管的是「跳过 anydoc 直接走本档」,
|
|
51
58
|
* 与这里「anydoc 试过、失败、再换」是两件事。
|
|
52
59
|
*/
|
|
53
60
|
export declare function resolveFallbackAdapter(cause: unknown, request: ConvertRequest, filename: string, inputPath: string | undefined): Promise<FallbackDecision | undefined>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAMpD,OAAO,EAAgB,KAAK,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEjF,eAAO,MAAM,QAAQ,EAAE,OAAO,EAA2E,CAAA;AAEzG,4CAA4C;AAC5C,eAAO,MAAM,cAAc,QAAQ,CAAA;AAEnC,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAE3D;AAED,mFAAmF;AACnF,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBjE;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,UAEhC,CAAA;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,OAAO,CAAC,CAkClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAYvC;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,CAAA;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,YAAY,EAAE,mBAAmB,CAAA;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,iBAAiB,EAAE,CAAA;IAC7B,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,gFAAgF;AAChF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAyClE"}
|
package/lib/adapters/index.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* pass-through. Unknown extensions are only treated as text when the bytes are
|
|
7
7
|
* actually textual — guessing there is how "converted successfully" becomes a lie.
|
|
8
8
|
*
|
|
9
|
-
* `.pdf` 默认仍走 anydoc(
|
|
10
|
-
* {@link resolveFallbackAdapter} 当场改走 `scanned`(见那里的理由)
|
|
9
|
+
* `.pdf` 默认仍走 anydoc(整份都是文本层时质量最好、零依赖);它报 `needsOcr` 时由
|
|
10
|
+
* {@link resolveFallbackAdapter} 当场改走 `scanned`(见那里的理由)。改走之后是**逐页**
|
|
11
|
+
* 判断:有文本层的页仍然用文本层,只有图片页才渲染 + OCR —— 所以"32 页里有 1 页是图片"
|
|
12
|
+
* 不会把另外 31 页的无损文字换成 OCR 结果。
|
|
11
13
|
*/
|
|
12
14
|
import { open } from 'node:fs/promises';
|
|
13
15
|
import { extname } from 'node:path';
|
|
@@ -69,8 +71,6 @@ export async function resolveAdapter(request, filename, inputPath) {
|
|
|
69
71
|
}
|
|
70
72
|
if (request.kind === 'text')
|
|
71
73
|
return textAdapter;
|
|
72
|
-
if (request.kind === 'url')
|
|
73
|
-
return webAdapter;
|
|
74
74
|
const extension = extname(filename).toLowerCase();
|
|
75
75
|
// 另存下来的页面按「网页」处理,而不是当纯文本直通 —— 否则 `.html` 会以源码形态入库。
|
|
76
76
|
if (WEB_FILE_EXTENSIONS.includes(extension))
|
|
@@ -86,12 +86,17 @@ export async function resolveAdapter(request, filename, inputPath) {
|
|
|
86
86
|
return textAdapter;
|
|
87
87
|
if (inputPath && (await looksTextual(inputPath)))
|
|
88
88
|
return textAdapter;
|
|
89
|
-
throw new AdapterError('FORMAT_UNSUPPORTED', `不支持的输入格式:${extension || '(无扩展名)'}`, `本版做文件类:${ANYDOC_EXTENSIONS.join(' ')}(文档)、${SCANNED_EXTENSIONS.join(' ')}(扫描件 OCR 档)、${IDDOC_EXTENSIONS.join(' ')}(音视频/图片)、${WEB_FILE_EXTENSIONS.join(' ')}(另存的页面)以及 ${TEXT_EXTENSIONS.join(' ')}
|
|
89
|
+
throw new AdapterError('FORMAT_UNSUPPORTED', `不支持的输入格式:${extension || '(无扩展名)'}`, `本版做文件类:${ANYDOC_EXTENSIONS.join(' ')}(文档)、${SCANNED_EXTENSIONS.join(' ')}(扫描件 OCR 档)、${IDDOC_EXTENSIONS.join(' ')}(音视频/图片)、${WEB_FILE_EXTENSIONS.join(' ')}(另存的页面)以及 ${TEXT_EXTENSIONS.join(' ')}(直通)。链接请先取回页面(浏览器另存为 HTML)再按文件提交。`);
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* 失败了要不要**当场换一个档再试一次**。
|
|
93
93
|
*
|
|
94
|
-
* 今天只有一条:anydoc 报 `needsOcr` 且输入是 `.pdf` → 改走 `scanned
|
|
94
|
+
* 今天只有一条:anydoc 报 `needsOcr` 且输入是 `.pdf` → 改走 `scanned`(PDF 逐页混合档)。
|
|
95
|
+
*
|
|
96
|
+
* **注意 anydoc 的判据是"整份文档"**:它只要发现**任意一页**需要 OCR 就整份放弃,
|
|
97
|
+
* 报出来的那句 `page 1 of 32 needs OCR` 说的其实是"第 1 页就是图片,我不做了",
|
|
98
|
+
* 而不是"32 页里只有第 1 页有问题"。所以换档不是"退而求其次",而是把 31 页本来
|
|
99
|
+
* 可以无损读出的文本层救回来 —— 换过去的档逐页判断,有文本层的页仍然走文本层。
|
|
95
100
|
*
|
|
96
101
|
* 为什么是自动的、而不是像 0017 原来写的那样要求用户显式指定 adapter:
|
|
97
102
|
*
|
|
@@ -102,10 +107,10 @@ export async function resolveAdapter(request, filename, inputPath) {
|
|
|
102
107
|
* 2. **它不改变「不静默降级」这条底线。** 换档会写进任务消息(面板上看得到)与
|
|
103
108
|
* `manifest.warnings`(`ADAPTER_FALLBACK`),并且产物里带 `SCANNED_TIER` 警告说明
|
|
104
109
|
* 这一档不做版面还原。用户看到的是「换了什么、为什么换、代价是什么」,不是「假装成功」。
|
|
105
|
-
* 3. **代价可控。**
|
|
110
|
+
* 3. **代价可控。** 这一档全程本地(pypdfium2 + RapidOCR,不出网),只在 uvx 存在时才走;
|
|
106
111
|
* uvx 不在就原样抛出 anydoc 的错误。
|
|
107
112
|
*
|
|
108
|
-
* 仍然保留 `SCANNED_OCR=1` 那条显式出口:它管的是「跳过 anydoc
|
|
113
|
+
* 仍然保留 `SCANNED_OCR=1` 那条显式出口:它管的是「跳过 anydoc 直接走本档」,
|
|
109
114
|
* 与这里「anydoc 试过、失败、再换」是两件事。
|
|
110
115
|
*/
|
|
111
116
|
export async function resolveFallbackAdapter(cause, request, filename, inputPath) {
|
|
@@ -123,7 +128,7 @@ export async function resolveFallbackAdapter(cause, request, filename, inputPath
|
|
|
123
128
|
return undefined;
|
|
124
129
|
return {
|
|
125
130
|
adapter: scannedAdapter,
|
|
126
|
-
reason: '这份 PDF
|
|
131
|
+
reason: '这份 PDF 里有图片页,anydoc 不做 OCR,已改用本地 PDF 档(逐页判断:有文本层的页仍用文本层,只有图片页走 OCR;不做版面还原)',
|
|
127
132
|
};
|
|
128
133
|
}
|
|
129
134
|
/** Everything the console and the DSH settings panel need to tell the truth. */
|
|
@@ -158,7 +163,6 @@ export async function capabilityReport() {
|
|
|
158
163
|
defaults: {
|
|
159
164
|
document: anydoc.available ? 'anydoc' : '(不可用)',
|
|
160
165
|
text: 'text',
|
|
161
|
-
url: 'web',
|
|
162
166
|
video: 'iddoc',
|
|
163
167
|
audio: 'iddoc',
|
|
164
168
|
image: 'iddoc',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* scanned ——
|
|
2
|
+
* scanned —— PDF 的**逐页混合档**(issue 0017 的扫描件线)。
|
|
3
3
|
*
|
|
4
4
|
* 与 `anydoc` 的分工是明确的:
|
|
5
5
|
*
|
|
6
|
-
* - `.pdf` **默认仍走 anydoc**(
|
|
7
|
-
* - anydoc 报 `needsOcr`(
|
|
6
|
+
* - `.pdf` **默认仍走 anydoc**(整份都是文本层时零依赖、质量最好);
|
|
7
|
+
* - anydoc 报 `needsOcr`(文档里**有**图片页)时,内核自动换到本档(`resolveFallbackAdapter`);
|
|
8
8
|
* - 设 `SCANNED_OCR=1` 可让 `.pdf` 直接走本档(显式 opt-in,不做静默降级)。
|
|
9
9
|
*
|
|
10
|
+
* ## 本档的核心是"逐页"而不是"整份"
|
|
11
|
+
*
|
|
12
|
+
* anydoc 在**整份文档**这一层判断,所以"32 页里有 1 页是图片"会让 32 页一起失败。
|
|
13
|
+
* 本档逐页问"这页有文本层吗":有就取文本、不渲染;没有才渲染页图并 OCR。
|
|
14
|
+
* 于是混合 PDF(扫描封面 + 电子正文)既能拿到全部 32 页,又不会把 31 页无损文字
|
|
15
|
+
* 换成 OCR 结果。
|
|
16
|
+
*
|
|
10
17
|
* 本档**不做**版面还原:输出是"第 N 页 + 该页图片 + 该页文字"。版面顺序、表格结构、
|
|
11
18
|
* 公式属于 0017 的"可选高质量档"(docling / MinerU),不在这一档的承诺里。
|
|
12
19
|
*
|
|
13
20
|
* 能力来自同包 `src/media/` 的 `convertScannedDocument()`:
|
|
14
|
-
* pypdfium2 渲染页图(无系统 poppler 依赖)
|
|
21
|
+
* pypdfium2 取文本 + 渲染页图(无系统 poppler 依赖)、RapidOCR 本地识别,全程不联网。
|
|
15
22
|
*
|
|
16
23
|
* 环境变量:
|
|
17
24
|
* SCANNED_OCR=1 让 `.pdf` 直接走本档(默认 false:先让 anydoc 试文本层)
|
|
18
25
|
* SCANNED_MAX_PAGES 最多处理页数(默认 50)
|
|
19
|
-
* SCANNED_DPI 渲染 DPI(默认 150)
|
|
26
|
+
* SCANNED_DPI 渲染 DPI(默认 150),只影响需要 OCR 的页
|
|
27
|
+
* SCANNED_OCR_THRESHOLD 文本层非空白字符数低于它就当作图片页(默认 16)
|
|
20
28
|
* SCANNED_NO_PAGE_IMAGES=1 只保留文字,不落地页图
|
|
21
29
|
* SCANNED_UVX / SCANNED_PYTHON 自定义 uvx 与 Python 版本
|
|
22
30
|
*/
|
|
23
31
|
import { type Adapter } from './types.js';
|
|
24
32
|
export declare const SCANNED_EXTENSIONS: string[];
|
|
25
33
|
export declare const scannedAdapter: Adapter;
|
|
26
|
-
/** 路由用:是否让 `.pdf`
|
|
34
|
+
/** 路由用:是否让 `.pdf` 直接走本档(显式 opt-in)。 */
|
|
27
35
|
export declare function scannedPreferred(): boolean;
|
|
28
36
|
//# sourceMappingURL=scanned.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanned.d.ts","sourceRoot":"","sources":["../../src/adapters/scanned.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scanned.d.ts","sourceRoot":"","sources":["../../src/adapters/scanned.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAWH,OAAO,EAAgB,KAAK,OAAO,EAAyF,MAAM,YAAY,CAAA;AAE9I,eAAO,MAAM,kBAAkB,UAAW,CAAA;AAqG1C,eAAO,MAAM,cAAc,EAAE,OAQ5B,CAAA;AAED,uCAAuC;AACvC,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
|