# 📖 拍书取字 —— 手机拍照 / PC 传图 → 服务器提取文本（OCR）

手机**连拍**实体书页、或 PC **批量上传图片**，本机服务器自动 OCR 提取文本，并可**一次性合成整书完整文本**。**本地 RapidOCR 离线识别为主**（免费、无次数限制），页面可选 **DeepSeek VL 云端增强/修正**（复杂版面、低质量照片效果更好，按 API 用量计费）。照片与原页文本自动按书名存档。

> 当前运行中（单一 HTTP 入口，无需证书）：
> - 手机端：**http://192.168.31.76:8901/**（📷 拍照 / 📖 相册连拍）
> - PC 端：**http://192.168.31.76:8901/pc**（🗂 图片批量上传 / 拖拽 + ✨ VL 修正 + 手动编辑）
> - 本项目目录（下载中心可浏览/下载存档）：http://192.168.31.76:8899/23-拍照OCR取字/
>
> 两页数据互通（同一存档目录）：手机补拍的页，PC 端「含历史页」合成即可并入；PC 修正好文本，手机端重新合成同样生效。

## 一、PC 端怎么用（图片上传 + 项目管理 + VL 修正）

打开 **http://192.168.31.76:8901/pc**（无需摄像头，两个页签：🖥 上传识别 / 🗂 项目管理）。

**页签一：上传识别**
1. 填「书名/标签」——与已有项目同名即**追加到该项目**；选默认引擎（本地 OCR 或 VL）
2. 点上传框 **🖼️ 选择图片（可多选）**，或把书页照片（手机/相机拍的）**直接拖进窗口任意位置**
3. 逐张入队自动识别（或选「⏸ 先全传完再识别」后点 ▶ 统一开工）

**页签二：项目管理（管理手机端产生的项目）**
1. 左侧列出服务器 `data/books/` 下**全部项目**（含手机端拍摄产生）：页数 / 字数 / 最近更新时间，点「打开」
2. 打开后逐页查看（缩略图、原图、文本），每页可：
   - **✨ VL 修正 / 🔤 本地重跑**：对该页存档照片重新识别，核对后「💾 保存修正」
   - 文本框**直接编辑**错字/段落后保存；**🗑 删页**清理重拍废页
3. 当前项目卡片：**📄 合成整书文本**（含历史页、采用修正后文本，下载链接同时进下载中心）、📋 复制全部、
   **✏️ 重命名**、**🗑 删除项目**（整个项目连同照片文本一并删除，需确认）
4. 手机补拍的页会出现在该项目列表（刷新即可见），点开即可在 PC 上修正

> 提示：手机页与 PC 页数据同目录互通；PC 上「含历史页/合成整书」都只读服务器存档，不依赖浏览器会话。

## 二、手机端怎么用（30 秒上手）

1. 手机连上局域网（Wi-Fi），浏览器打开 **http://192.168.31.76:8901/**
2. 顶部选引擎：**本地 OCR**（默认）或 **✨ DeepSeek VL 增强**（识别节奏按整批统一生效）
3. 填「书名/标签」——服务器按它分目录存档，整书文本也按它合成
4. **拍照/选图即上传**（不排队）：
   - 📷 拍照上传 / 🖼️ 相册多选上传 —— 照片**立刻传到服务器存档**（状态"待识别"）
   - 识别由**服务器队列逐条处理**：页面每 2 秒自动刷新，卡片显示 排队中 → 识别中 → 完成/失败
   - 手机刷新页面或换设备都不丢：自动恢复上次拍摄的书
5. 失败的页可 **↻ 重试**（重新排队）；每页可 📋 复制 / 💾 下载 txt / 🖼️ 原图 / ✕ 删除（进回收站）
6. 全部识别完点 **📄 合成整书文本**：服务器按页序合成该书全部已识别页（未识别/失败的页会提示不并入，处理完再合成一次即可），⬇️ 下载或到下载中心取回
7. 每页操作：📋 复制 / 💾 下载本页 txt / 🖼️ 查看原图 / ✕ 删除（进回收站）；底部可 📋 复制全部 / 🗑 删除本书（进回收站）
8. 存档：每页照片与 .txt 落在服务器 `data/books/<书名>/`，也能在下载中心
   http://192.168.31.76:8899/23-拍照OCR取字/data/books/ 直接浏览下载

拍摄技巧：书页放平、正对光源避免反光、别让手指/影子进画面、尽量拍全整页（竖拍优先）。

> ⚠️ 局域网是 **HTTP**（非 HTTPS），浏览器不允许网页直接调起"取景框预览"，
> 所以手机「拍照识别」走系统相机（`capture` 属性），安卓/iOS 均可正常弹起相机。
> 若某浏览器没弹相机，用「相册选图」先拍好再选即可；PC 页本就是上传图片，不受影响。

## 三、部署与启停（本机 192.168.31.76）

```bash
cd /home/zyw/Downloads/dl-hub/23-拍照OCR取字
bash start.sh    # 启动（后台，日志 server.log，PID 存 server.pid）
bash stop.sh     # 停止
```

- 端口：8901（环境变量 `OCR_PORT` 可改；改端口记得改 `start.sh` 提示文案）
- 访问地址 = `http://<本机局域网IP>:8901/`（手机）与 `http://<本机局域网IP>:8901/pc`（PC）

首次部署（重建环境，一般无需）：

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt   # flask / pillow / requests / rapidocr-onnxruntime
```

## 四、两个识别引擎

| 引擎 | 速度/成本 | 特点 | 适用 |
|---|---|---|---|
| 🔤 本地 RapidOCR（默认） | ~3–8 秒/页，免费离线 | 印刷体中英文识别准、无网络依赖、无次数限制 | 绝大多数清晰的印刷书页 |
| ✨ DeepSeek VL 增强 | ~10–20 秒/页，按 token 计费 | 视觉大模型整页阅读，还原空格/标点/版面更好，抗模糊反光更强 | 本地效果不满意、复杂版式、拍照质量差的页 |

- VL 走官方 API `api.deepseek.com`（模型 `deepseek-v4-flash-vision-exp`），key 存于同目录
  `deepseek_key.txt`（权限 600）。**该文件含密钥，勿外传、勿上传到公开位置。**
- 删掉 `deepseek_key.txt` 即关闭 VL（页面开关自动变灰并回退本地）。

## 五、目录结构

```
23-拍照OCR取字/
├── server.py            # 后端：Flask + RapidOCR + DeepSeek VL（单文件）
├── static/index.html    # 移动端页面（单文件，内联 CSS/JS）
├── static/index_pc.html # PC 端页面（图片上传 + VL 修正，单文件）
├── requirements.txt     # Python 依赖
├── start.sh / stop.sh   # 启停脚本
├── deepseek_key.txt     # DeepSeek API key（敏感，勿外传）
├── server.log           # 运行日志
├── data/books/<书名>/   # 每页存档：第xxx页_时间.jpg + 同名 .txt + 全书文本_*.txt
└── test/                # 自测脚本与样例图（gen_sample.py / sample_book_page.png）
```

## 六、HTTP 接口

| 接口 | 说明 |
|---|---|
| `GET /` | 移动端页面 |
| `GET /pc` | PC 端页面（上传 + 项目管理 + VL 修正） |
| `GET /api/health` | 服务状态：`{ok, local_engine, vl_enabled, vl_model}` |
| `POST /api/ocr` | 【旧流程，保留兼容】multipart：`image`、`engine`、`book_name`、`seq` → 上传+识别一次完成 |
| `POST /api/photo` | 【推荐】multipart：`files[]`(多张)+`book_name`+`engine` → **照片即时存档**并自动入识别队列，返回 `{book, items:[{seq,stem}], failed}` |
| 上传限制 | **单次 ≤100 张、每张 ≤10MB**（请求体上限 ≈1.02GB）；超限返回 413 + JSON 错误说明。手机页会**自动分批**（每批 ≤100 张且 ≤900MB）依次上传，无需手动拆批 |
| `POST /api/batch/process` | JSON：`{book_name?, retry_errors?}` → 把待识别/失败页排入服务器识别队列（逐条处理） |
| `POST /api/batch/vlall` | JSON：`{book_name, scope:"all"\|"todo", retry_errors?}` → 整书 VL 批量：标记为结构化 VL 并排队（scope=all 重跑全部页 / todo 只补待识别失败页） |
| `POST /api/page/reocr` | JSON：`{book_name, stem, engine, save?}` → 对已存档页用指定引擎**重跑**（VL 修正/本地重跑）；`save:true` 时覆盖该页存档 txt |
| `POST /api/page/text` | JSON：`{book_name, stem, text}` → **手动修正**，覆盖存档 txt（合成即采用） |
| `POST /api/page/delete` | JSON：`{book_name, stem}` → **删除一页**（jpg+txt，不可恢复） |
| `POST /api/batch/export` | JSON：`{book_name, scope:"session"\|"folder", stems?, include_side?:0\|1}` → 服务器**按印刷页码自动排序**合成整书文本（VL 页按页码、其余按拍摄顺序），可选含页眉页脚；返回 `{ok, pages, chars, file, url, missing, skipped, ordered_by, vl_pages, preview}` |
| `POST /api/batch/exportall` | JSON：`{book_name}` → **一键生成全套成品 4 份**（正文标注版/纯净全文/含页眉页脚 txt/含页眉页脚 md）到存档目录，返回 `{files:[{tag,name,url,chars}], dropped, skipped, ordered_by}` |
| `GET /api/books` | 项目管理：列出全部项目 `{books:[{name, pages, chars, size, updated_at, first}]}`（含手机端产生的） |
| `GET /api/books/<书>/pages[?text=1]` | 项目管理：某项目全部页（页码/时间/缩略图/txt 链接；`text=1` 附每页全文供修正） |
| `POST /api/books/delete` | JSON：`{book_name}` → 整个项目移入**回收站**（data/trash，可恢复），非永久删除 |
| `POST /api/books/rename` | JSON：`{book_name, new_name}` → 重命名项目目录 |
| `GET /api/trash` | 回收站列表：`{entries:[{entry, kind, book, stem, files, size, time}]}` |
| `POST /api/trash/restore` | JSON：`{entry}` → 从回收站恢复整书或单页 |
| `POST /api/trash/purge` | JSON：`{entry}` → 永久删除回收站条目（不可恢复） |
| `GET /f/<书名>/<页>.jpg[?w=480]` | 原图 / 实时缩略图 |
| `GET /txt/<书名>/<页或全书>.txt` | 下载单页 / 整书文本 |

## 七、已知边界（v1 说明）

- **版面**：单栏正文页效果最佳；双栏/表格/图片环绕等复杂版面本地引擎按"从上到下、从左到右"拼接，
  可能有错序，此时建议用 **VL 增强**。
- **倾斜/透视**：斜拍会拉低本地准确率；尽量端平，或换 VL。
- **繁体/竖排古籍**：未做专门模型，繁体支持有限；竖排需 VL 且提示词可再调。
- 页面上删除卡片**不会**删除服务器已存档的文件（防误删）；如需清理直接删 `data/books/<书名>/`。
- 无鉴权、纯局域网服务，请勿把 8901 端口暴露到公网。

## 八、更新记录

### v6（2026-09-09）VL 整书批量 + 页码/页眉/页脚 + 自动排序
- **结构化 VL**：DeepSeek VL 整页识别输出 四段（页码/页眉/页脚/正文），写 `data/books/<书>/<页>.vl.json`；
  正文 = 剔除页眉页脚页码后的阅读顺序文本（.txt）。
- `POST /api/batch/vlall {book_name, scope:all|todo}`：一键把整本（或待处理）页标为 VL 并排队逐条处理，进度实时可见。
- **整书自动排序**：合成时优先按 VL 识别出的印刷页码排列（无 VL 页按拍摄顺序）；合成响应含 `ordered_by`/`vl_pages`。
- **页眉页脚**：导出可选 `include_side=1` 生成「含页眉页脚版」（【页眉】…正文…【页脚】…）；默认正文版已剔除。
- 上传接口 VL 引擎一律走结构化（engine 映射 vl2）；单页修正 `reocr engine=vl` 亦结构化并返回页码/页眉/页脚。
- 页面：手机「合成整书文本（正文·按页码排序）」「含页眉页脚版」；PC 项目管理「✨ VL 整书处理/只补待处理页」、
  卡片显示 📌VL 原书页码/页眉/页脚、整书文本(正文/含页眉页脚)。
- 实测（合成书页 第23页）：VL 识别 页码=23、页眉="第十二章·山间来信"、页脚=无；正文不含页眉；
  合成 ordered_by=page_num、分隔行标注"原书页码 23"；含页眉页脚版带【页眉】行。
- v6.1 修正（真实 15 页案例驱动）：
  1) **章首页置前**：无印刷页码且正文以"第N章"开头的页判定为章首页/书前页，自动排到整章最前；
  2) **重复拍摄自动去重**：正文相似度>0.9 的页只保留 1 份（导出头部注明合并了哪几页；存档照片不删）；
  3) **不再"空成功"**：结构化 VL 输出为空时自动回退整页普通 VL；两路皆空则记为失败（err.json）可重试，
     不再把空页当"完成"制造"未识别出文字"占位。真实案例 15 页：3 页空→全部重跑恢复，14 页/12577 字，无占位符。
- v6.2（功能化）「一键导出全套成品」：新增 `POST /api/batch/exportall {book_name}`，服务器一次生成 4 份到存档目录并返回链接：
  **正文标注版.txt**（带页码标注分隔）/ **纯净全文.txt**（无任何装饰行）/ **含页眉页脚.txt** / **含页眉页脚.md**（每页一个小节 + 页眉页脚引用样式）；
  页面（PC 项目管理、手机整书卡片）均加「📦 一键导出全套成品」按钮；页文件名识别收紧为"第NNN页_…"避免把成品文件误当页。

### v5（2026-09-09）上传不排队：先传服务器、后排队识别
- 流水线改为「**照片即传即存 + 服务器排队逐条识别**」：上传和识别解耦——
  手机拍照/多选后照片立刻存档到服务器（状态=待识别），OCR 由服务器后台队列逐条处理
  （排队中→识别中→完成/失败），手机与 PC 通过轮询页状态实时看到进度。
- 新增 `POST /api/photo`（多图即时上传存档，自动入识别队列）、`POST /api/batch/process`
  （把待识别/失败页重新排入队列）。页状态由磁盘推导：jpg 在=照片已上服务器；+txt=完成；+err.json=失败。
- 手机页重写：卡片状态来自服务器（刷新/换机不丢），自动恢复上次拍摄的书；支持失败重试、
  删除进回收站、整书合成。
- PC 项目管理同步：打开项目自动把待识别页入队，页面每 6 秒静默刷新状态；每页可 ▶VL/本地识别、
  保存、删页；统计分 完成/处理中/失败。
- `/api/books`、`/api/books/<书>/pages` 返回新增 `done/todo/errors/status/err` 等字段；
  `/api/batch/export`（folder）会报告 `skipped`（未识别/失败未并入的页）。

### v4.2（2026-09-09）删除安全：回收站
- 整书/单页删除一律先进 **data/trash 回收站**，PC 项目管理可恢复/永久删除；
  新增 `GET /api/trash`、`POST /api/trash/restore`、`POST /api/trash/purge`。
  （历史教训：早期联调 rm 清理过测试目录，可能误删同名用户目录——现已改为回收站制。）

### v4.1（2026-09-09）体验修正
- PC 顶栏「🗂 项目 N」徽标每 8 秒自动轮询；手机页服务器连接状态条。
- 注：此时间点已确认用户手机曾成功上传 **15 页**（存于 data/books/未命名书_20260909，全部识别完成），属真实用户数据，保留不清理。

### v4（2026-09-09）PC 项目管理（管理手机端项目）
- PC 页新增「🗂 项目管理」页签：列出服务器全部项目（含手机端拍摄，页数/字数/更新时间），
  打开后可逐页 **VL 修正 / 本地重跑 / 手动编辑保存 / 删页**；项目级支持 **合成整书文本**（含历史页）、
  复制全部、**重命名**、**删除项目**。
- 新增接口：`GET /api/books`、`GET /api/books/<书>/pages?text=1`、`POST /api/books/delete`、
  `POST /api/books/rename`、`POST /api/page/delete`。
- 手机端页面不变，数据互通：手机拍的页刷新即出现在 PC 项目列表。

### v3（2026-09-09）PC 端：图片上传 + VL 修正
- 新增 PC 页面 `GET /pc`（`static/index_pc.html`）：**纯图片上传**（多选 + 整窗拖拽），不依赖摄像头/HTTPS；
  批量队列与识别节奏同手机版；每页支持 **✨ VL 修正 / 🔤 本地重跑 / 手动编辑 → 💾 保存修正**，
  修正写回服务器存档，整书合成自动采用。
- 新增 `POST /api/page/reocr`（对存档页重跑指定引擎，可选落盘）、`POST /api/page/text`（手动覆盖存档文本）。
- 手机页与 PC 页互链；数据同目录互通。
- （曾为 PC 摄像头版引入 HTTPS 8902 与自签证书，后按需求移除，保持单 HTTP 入口。）

### v2（2026-09-09）批量连拍 + 整书文本
- 页面改为**批量队列**：拍照/相册多选全部入队，卡片显示 待识别/识别中/完成/失败；
  识别节奏可选「📸 拍完即识别（边拍边识别）」或「⏸ 先拍完再识别（点 ▶ 统一开工）」。
- 新增 `POST /api/batch/export`：**服务器端一次合成整书文本**（session=本次拍摄按页序，
  folder=含历史页），落盘 `全书文本_<时间>.txt` 并返回下载链接；同秒多次合成自动加序号不覆盖。
- `/api/ocr` 响应新增 `stem` 字段供合成引用。

## 九、联调记录（2026-09-09）

- 合成测试页（中文段落+英文句）本地 OCR：13 行 / 285 字 / 约 5.5 秒，中文与标点还原良好。
- HTTP 全链路：本地 6.3 秒、VL 11.7 秒（DeepSeek `deepseek-v4-flash-vision-exp`，
  prompt 544 + completion 1348 tokens），存档/缩略图/文本下载路由均 200。
- 批量合成验证：2 页 session 合成 606 字、按传入页序输出；folder 合成含历史页同样正常；同秒二次合成自动命名 `_2.txt` 不覆盖。
- v3 验证：`reocr` VL 重跑正常（save:false 不落盘 / save:true 覆盖存档，输出含 VL 空格特征）；
  `page/text` 手动覆盖后 session 合成输出人工文本；合成文件为「修正后文本」。
- v4 验证：列表 2 页/606 字、打开含全文、删页后计数更新、重命名成功、folder 合成含历史页 1 页/303 字、
  删项目后列表清空；路径穿越防护正常。

### v6.3（2026-09-23）上传上限调整：100 张 × 10MB

- `MAX_CONTENT_LENGTH` 由 **40MB**（旧的"单图上限"注释其实是整请求上限）提升为 **100×10MB＋16MB ≈ 1.02GB**；
- 新增**单次张数上限 100**（超出返回 400 + 明确提示）与**单张 10MB 上限**（超限只跳过该张并在 `failed[]` 说明原因，不拖垮整批）；
- 新增 **413 JSON 错误处理**：超限时返回可读中文说明，前端不再显示"网络错误：Unexpected token"；
- 手机页上传改为**自动分批**（每批 ≤100 张且 ≤900MB）依次上传并显示"第 i/n 批"进度，用户选几百张也无需手动拆批；
- 三个上限均可用环境变量调整：`OCR_MAX_FILES`（默认 100）、`OCR_MAX_FILE_MB`（默认 10）、`OCR_MAX_UPLOAD_MB`（默认 100×10+16）；
- 实测：101 张 → 400 提示；单张 24.9MB → 跳过并说明；8 张共 44.8MB 单请求 → 全部成功（旧版会 413）；20MB 上限实例 → 413 但返回 JSON 说明。

### v6.4（2026-09-23）表格还原 + 空结果保护

- **VL 提示词新增表格规则**：页面中的表格（含“续表”、项目章程表、组织结构表、会议记录表、职责/进度/预算表）一律用 **Markdown 管道表格**逐行转写（表头 + `| --- |` 分隔 + 数据行），不再拍平成用“/”或空格分隔的纯文本；并规定单元格内不用 `<br>`、同格多条用“；”、不去重重复行、照抄真实表头。
- 实测：同一份 18 页书稿，升级前导出的 MD **0 行表格**，升级后 **105 行 Markdown 表格**（覆盖 8 个含表页）。
- **新增安全保存闸 `_save_page_text()`**：VL/本地识别结果为**空**、或比现有正文**骤减到 <30%** 时**拒绝覆盖**，改为写 `err.json` 并提示重试（`force=true` 可强制覆盖）。
  - 修复背景：本次重跑时 VL 偶发返回空正文，旧实现无条件覆盖，把两页已识别好的正文清空（已从备份恢复并重跑）。
  - `POST /api/page/reocr` 在触发闸门时返回 **422** + 说明，不再静默写坏数据。
