feat: add OCR language extension configuration and documentation

- Introduced `compose.ocr-languages.yml` for installing additional Tesseract OCR language packs during container startup.
- Updated documentation to include new OCR language extension options and usage scenarios.
- Enhanced existing documentation with references to the new OCR language extension guide.
- Improved test coverage for the deark converter and formats, ensuring accurate format handling and error management.
- Refactored code to streamline file system operations and improve readability.
This commit is contained in:
Your Name 2026-01-24 00:16:32 +08:00
parent 737e925ac7
commit a4489f4945
11 changed files with 766 additions and 90 deletions

View file

@ -0,0 +1,345 @@
# OCR 語言擴展指南
ConvertX-CN 預設內建 7 種 OCR 語言(+ 自動多語言模式),但你可以透過多種方式擴展支援更多語言。
---
## 內建語言
以下語言已預先安裝,無需額外配置:
| 語言 | Tesseract 代碼 | UI 格式代碼 |
| -------- | -------------- | ----------- |
| 英文 | `eng` | `pdf-en` |
| 繁體中文 | `chi_tra` | `pdf-zh-TW` |
| 簡體中文 | `chi_sim` | `pdf-zh` |
| 日文 | `jpn` | `pdf-ja` |
| 韓文 | `kor` | `pdf-ko` |
| 德文 | `deu` | `pdf-de` |
| 法文 | `fra` | `pdf-fr` |
另外還有 `pdf-ocr` 自動多語言模式,會同時使用所有已安裝的語言包。
---
## 擴展方法比較
| 方法 | 優點 | 缺點 | 適用場景 |
| --------------------------- | ---------------------- | ------------------ | -------------- |
| **方法一compose.yaml** | 簡單、無需建立額外檔案 | 每次啟動需重新安裝 | 測試、臨時使用 |
| **方法二:掛載語言包** | 離線可用、啟動快 | 需手動下載檔案 | 離線環境 |
| **方法三:自訂 Dockerfile** | 一次安裝、啟動最快 | 需要建立自訂 image | 生產環境 |
---
## 方法一:透過 compose.yaml 安裝
### 完整範例
```yaml
# compose.yaml
services:
convertx:
image: ghcr.io/cysk003/convertx-cn:latest
container_name: convertx-cn
restart: unless-stopped
# 使用自訂 entrypoint 安裝額外語言包
entrypoint: ["/bin/sh", "-c"]
command:
- |
echo "📦 正在安裝額外 OCR 語言包..."
apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr-spa \
tesseract-ocr-ita \
tesseract-ocr-por \
tesseract-ocr-rus \
&& rm -rf /var/lib/apt/lists/*
echo "✅ 語言包安裝完成"
echo "🚀 啟動 ConvertX..."
exec bun run start
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Taipei
- JWT_SECRET=請改成你自己的長隨機字串-至少32個字元
- ACCOUNT_REGISTRATION=true
- HTTP_ALLOWED=true
- ALLOW_UNAUTHENTICATED=false
- AUTO_DELETE_EVERY_N_HOURS=24
```
### 說明
1. `entrypoint``command` 覆蓋了原本的啟動命令
2. 在啟動應用前先執行 `apt-get install` 安裝語言包
3. `exec bun run start` 確保應用正確啟動並接收信號
### 注意事項
> ⚠️ **每次容器重啟都會重新安裝語言包**,首次啟動可能需要 1-2 分鐘。
> 💡 如果需要更快的啟動速度,請使用方法二或方法三。
---
## 方法二:掛載語言包(推薦離線環境)
### 步驟 1下載語言包
```bash
# 建立 tessdata 目錄
mkdir -p tessdata
cd tessdata
# 下載需要的語言包
# 高精度版(推薦,檔案較大)
wget https://github.com/tesseract-ocr/tessdata_best/raw/main/spa.traineddata # 西班牙文
wget https://github.com/tesseract-ocr/tessdata_best/raw/main/ita.traineddata # 義大利文
wget https://github.com/tesseract-ocr/tessdata_best/raw/main/por.traineddata # 葡萄牙文
wget https://github.com/tesseract-ocr/tessdata_best/raw/main/rus.traineddata # 俄文
# 或使用標準版(檔案較小,速度較快)
# wget https://github.com/tesseract-ocr/tessdata/raw/main/spa.traineddata
```
### 步驟 2配置 compose.yaml
```yaml
# compose.yaml
services:
convertx:
image: ghcr.io/cysk003/convertx-cn:latest
container_name: convertx-cn
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./data:/app/data
# 掛載語言包目錄
- ./tessdata:/usr/share/tesseract-ocr/5/tessdata:ro
environment:
- TZ=Asia/Taipei
- JWT_SECRET=請改成你自己的長隨機字串-至少32個字元
```
### 目錄結構
```
your-project/
├── compose.yaml
├── data/
└── tessdata/
├── spa.traineddata
├── ita.traineddata
├── por.traineddata
└── rus.traineddata
```
### 語言包下載來源
| 版本 | 說明 | 下載位置 |
| -------- | -------------------- | ---------------------------------------------- |
| 高精度版 | 辨識準確度最高 | https://github.com/tesseract-ocr/tessdata_best |
| 標準版 | 平衡準確度與速度 | https://github.com/tesseract-ocr/tessdata |
| 快速版 | 速度最快,準確度較低 | https://github.com/tesseract-ocr/tessdata_fast |
---
## 方法三:自訂 Dockerfile推薦生產環境
### 步驟 1建立 Dockerfile
```dockerfile
# Dockerfile
FROM ghcr.io/cysk003/convertx-cn:latest
# 安裝額外 OCR 語言包
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr-spa \
tesseract-ocr-ita \
tesseract-ocr-por \
tesseract-ocr-rus \
tesseract-ocr-ara \
tesseract-ocr-hin \
tesseract-ocr-vie \
tesseract-ocr-tha \
&& rm -rf /var/lib/apt/lists/*
# 可選:驗證安裝
RUN tesseract --list-langs
```
### 步驟 2配置 compose.yaml
```yaml
# compose.yaml
services:
convertx:
build:
context: .
dockerfile: Dockerfile
container_name: convertx-cn
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Taipei
- JWT_SECRET=請改成你自己的長隨機字串-至少32個字元
```
### 步驟 3建置並啟動
```bash
# 建置自訂 image
docker compose build
# 啟動
docker compose up -d
```
### 目錄結構
```
your-project/
├── compose.yaml
├── Dockerfile
└── data/
```
---
## 可用語言包完整列表
### 常用語言
| Tesseract 包名 | 語言代碼 | 語言 | 檔案大小(約) |
| ------------------- | -------- | -------- | -------------- |
| `tesseract-ocr-spa` | es | 西班牙文 | 5 MB |
| `tesseract-ocr-ita` | it | 義大利文 | 4 MB |
| `tesseract-ocr-por` | pt | 葡萄牙文 | 4 MB |
| `tesseract-ocr-rus` | ru | 俄文 | 6 MB |
| `tesseract-ocr-ara` | ar | 阿拉伯文 | 3 MB |
| `tesseract-ocr-hin` | hi | 印地文 | 7 MB |
| `tesseract-ocr-vie` | vi | 越南文 | 2 MB |
| `tesseract-ocr-tha` | th | 泰文 | 5 MB |
### 歐洲語言
| Tesseract 包名 | 語言代碼 | 語言 |
| ------------------- | -------- | ------------ |
| `tesseract-ocr-swe` | sv | 瑞典文 |
| `tesseract-ocr-dan` | da | 丹麥文 |
| `tesseract-ocr-nor` | no | 挪威文 |
| `tesseract-ocr-fin` | fi | 芬蘭文 |
| `tesseract-ocr-pol` | pl | 波蘭文 |
| `tesseract-ocr-ces` | cs | 捷克文 |
| `tesseract-ocr-hun` | hu | 匈牙利文 |
| `tesseract-ocr-nld` | nl | 荷蘭文 |
| `tesseract-ocr-ell` | el | 希臘文 |
| `tesseract-ocr-ukr` | uk | 烏克蘭文 |
| `tesseract-ocr-ron` | ro | 羅馬尼亞文 |
| `tesseract-ocr-bul` | bg | 保加利亞文 |
| `tesseract-ocr-hrv` | hr | 克羅地亞文 |
| `tesseract-ocr-slk` | sk | 斯洛伐克文 |
| `tesseract-ocr-slv` | sl | 斯洛維尼亞文 |
### 中東語言
| Tesseract 包名 | 語言代碼 | 語言 |
| ------------------- | -------- | -------- |
| `tesseract-ocr-heb` | he | 希伯來文 |
| `tesseract-ocr-tur` | tr | 土耳其文 |
| `tesseract-ocr-fas` | fa | 波斯文 |
### 亞洲語言
| Tesseract 包名 | 語言代碼 | 語言 |
| ------------------- | -------- | ------------ |
| `tesseract-ocr-ben` | bn | 孟加拉文 |
| `tesseract-ocr-tam` | ta | 泰米爾文 |
| `tesseract-ocr-tel` | te | 泰盧固文 |
| `tesseract-ocr-kan` | kn | 卡納達文 |
| `tesseract-ocr-mal` | ml | 馬拉雅拉姆文 |
| `tesseract-ocr-mar` | mr | 馬拉地文 |
| `tesseract-ocr-guj` | gu | 古吉拉特文 |
| `tesseract-ocr-pan` | pa | 旁遮普文 |
| `tesseract-ocr-mya` | my | 緬甸文 |
| `tesseract-ocr-khm` | km | 高棉文 |
| `tesseract-ocr-lao` | lo | 寮文 |
| `tesseract-ocr-ind` | id | 印尼文 |
| `tesseract-ocr-msa` | ms | 馬來文 |
> 💡 **查詢所有可用語言包**:在容器內執行 `apt-cache search tesseract-ocr-`
---
## 驗證語言包安裝
### 方法 1在容器內檢查
```bash
# 進入容器
docker exec -it convertx-cn /bin/bash
# 列出已安裝的語言
tesseract --list-langs
```
### 方法 2透過日誌確認
啟動容器後查看日誌:
```bash
docker compose logs -f convertx
```
如果使用方法一compose.yaml 安裝),會看到類似以下輸出:
```
📦 正在安裝額外 OCR 語言包...
✅ 語言包安裝完成
🚀 啟動 ConvertX...
```
---
## 常見問題
### Q: 為什麼 UI 上看不到新增的語言選項?
**A:** OCRmyPDF 的 UI 格式是在程式碼中固定的8 種選項),新增語言包只會影響 `pdf-ocr` 自動模式的辨識能力。如果你使用 `pdf-ocr` 模式,系統會自動使用所有已安裝的語言包。
### Q: 自動模式會變慢嗎?
**A:** 安裝更多語言包後,`pdf-ocr` 自動模式可能會稍微變慢,因為 Tesseract 需要嘗試更多語言。如果你知道文件語言,選擇特定語言格式會更快。
### Q: 如何在 UI 上顯示新的語言選項?
**A:** 需要修改 `src/converters/ocrmypdf.ts` 中的 `SUPPORTED_LANGUAGES` 陣列,並重新建置應用。這超出了簡單配置的範圍。
### Q: 語言包太大,如何減少下載量?
**A:** 使用 `tessdata_fast` 版本而非 `tessdata_best`,或只安裝實際需要的語言。
---
## 相關文件
- [OCR 功能說明](../功能說明/OCR.md)
- [Docker Compose 說明](說明文件.md)
- [compose.ocr-languages.yml](compose.ocr-languages.yml)
- [環境變數設定](../配置設定/環境變數.md)

View file

@ -0,0 +1,96 @@
# ==============================================================================
# ConvertX-CN OCR 語言擴展配置
#
# 📚 此配置示範如何在容器啟動時安裝額外的 Tesseract OCR 語言包
#
# 🎯 適用情境:
# - 需要 OCR 辨識西班牙文、義大利文、俄文等額外語言
# - 翻譯引擎支援 15 種語言,但 OCR 預設只內建 8 種
# - 需要處理更多語言的掃描版 PDF
#
# ⚠️ 注意事項:
# - 每次容器啟動時都會安裝語言包,首次啟動較慢
# - 語言包會存在容器內,重啟後需重新安裝
# - 若需永久安裝,請使用自訂 Dockerfile 方式
#
# ==============================================================================
services:
convertx:
image: convertx/convertx-cn:latest
container_name: convertx-cn
restart: unless-stopped
# =========================================================================
# 使用自訂 entrypoint 安裝額外語言包
# =========================================================================
entrypoint: ["/bin/sh", "-c"]
command:
- |
echo "📦 正在安裝額外 OCR 語言包..."
apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr-spa \
tesseract-ocr-ita \
tesseract-ocr-por \
tesseract-ocr-rus \
&& rm -rf /var/lib/apt/lists/*
echo "✅ 語言包安裝完成"
echo "🚀 啟動 ConvertX..."
exec bun run start
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Taipei
- JWT_SECRET=請改成你自己的長隨機字串-至少32個字元
- ACCOUNT_REGISTRATION=true
- HTTP_ALLOWED=true
- ALLOW_UNAUTHENTICATED=false
- AUTO_DELETE_EVERY_N_HOURS=24
# ==============================================================================
# 可用語言包對照表
# ==============================================================================
#
# 💡 只需安裝你需要的語言,不必全部安裝
#
# 語言包名稱 | 語言代碼 | 語言
# ----------------------|---------|----------------
# tesseract-ocr-spa | es | 西班牙文
# tesseract-ocr-ita | it | 義大利文
# tesseract-ocr-por | pt | 葡萄牙文
# tesseract-ocr-rus | ru | 俄文
# tesseract-ocr-ara | ar | 阿拉伯文
# tesseract-ocr-hin | hi | 印地文
# tesseract-ocr-vie | vi | 越南文
# tesseract-ocr-tha | th | 泰文
# tesseract-ocr-swe | sv | 瑞典文
# tesseract-ocr-dan | da | 丹麥文
# tesseract-ocr-nor | no | 挪威文
# tesseract-ocr-fin | fi | 芬蘭文
# tesseract-ocr-pol | pl | 波蘭文
# tesseract-ocr-ces | cs | 捷克文
# tesseract-ocr-hun | hu | 匈牙利文
# tesseract-ocr-tur | tr | 土耳其文
# tesseract-ocr-heb | he | 希伯來文
# tesseract-ocr-ben | bn | 孟加拉文
# tesseract-ocr-tam | ta | 泰米爾文
# tesseract-ocr-ind | id | 印尼文
#
# ==============================================================================
# 內建語言(無需額外安裝)
# ==============================================================================
#
# tesseract-ocr-eng | en | 英文
# tesseract-ocr-chi-tra | zh-TW | 繁體中文
# tesseract-ocr-chi-sim | zh | 簡體中文
# tesseract-ocr-jpn | ja | 日文
# tesseract-ocr-kor | ko | 韓文
# tesseract-ocr-deu | de | 德文
# tesseract-ocr-fra | fr | 法文
#
# ==============================================================================

View file

@ -5,19 +5,22 @@
## 範例檔案 ## 範例檔案
| 檔案 | 適用情境 | 說明 | | 檔案 | 適用情境 | 說明 |
| ------------------------------------------------ | ----------- | --------------------- | | ------------------------------------------------------ | ----------- | --------------------- |
| [compose.minimal.yml](compose.minimal.yml) | Docker 老手 | 最精簡的可用配置 | | [compose.minimal.yml](compose.minimal.yml) | Docker 老手 | 最精簡的可用配置 |
| [compose.production.yml](compose.production.yml) | 生產環境 | 含 Reverse Proxy 設定 | | [compose.production.yml](compose.production.yml) | 生產環境 | 含 Reverse Proxy 設定 |
| [compose.reference.yml](compose.reference.yml) | 參考文件 | 所有設定的完整參考 | | [compose.reference.yml](compose.reference.yml) | 參考文件 | 所有設定的完整參考 |
| [compose.ocr-languages.yml](compose.ocr-languages.yml) | OCR 擴展 | 安裝額外 OCR 語言包 |
| [OCR語言擴展.md](OCR語言擴展.md) | 詳細指南 | OCR 語言擴展完整說明 |
## 快速選擇 ## 快速選擇
| 你是... | 使用 | | 你是... | 使用 |
| ------------ | ----------------------------- | | ----------------- | -------------------------------------------- |
| 新手 | [README 主頁](../說明文件.md) | | 新手 | [README 主頁](../說明文件.md) |
| Docker 熟手 | compose.minimal.yml | | Docker 熟手 | compose.minimal.yml |
| 生產環境 | compose.production.yml | | 生產環境 | compose.production.yml |
| 查詢所有選項 | compose.reference.yml | | 查詢所有選項 | compose.reference.yml |
| 需要更多 OCR 語言 | [OCR語言擴展.md](OCR語言擴展.md)(詳細指南) |
## 如何使用 ## 如何使用
@ -55,3 +58,17 @@ docker compose up -d
### 我想了解所有設定 ### 我想了解所有設定
參考 [compose.reference.yml](compose.reference.yml),包含所有環境變數的說明。 參考 [compose.reference.yml](compose.reference.yml),包含所有環境變數的說明。
### 我需要更多 OCR 語言支援
參考 [OCR語言擴展.md](OCR語言擴展.md),這是完整的 OCR 語言擴展指南,包含:
- 三種擴展方法的詳細說明與比較
- 完整的 compose.yaml 範例配置
- 50+ 種可用語言包列表
- 語言包下載與驗證方法
- 常見問題解答
> 💡 內建 OCR 語言:英文、繁體中文、簡體中文、日文、韓文、德文、法文
>
> 翻譯引擎支援 15 種語言,但 OCR 預設只內建 8 種

View file

@ -131,41 +131,47 @@ eng + chi_tra + chi_sim + jpn + kor + deu + fra
## 新增語言 ## 新增語言
### 方法一:自訂 Dockerfile ConvertX-CN 預設內建 7 種 OCR 語言,但你可以透過 Docker Compose 配置擴展支援更多語言。
> 💡 在 `Dockerfile.full` 中取消註解以下內容 ### 擴展方法
```dockerfile | 方法 | 說明 | 適用場景 |
RUN apt-get update && apt-get install -y --no-install-recommends \ | ----------------- | -------------------------- | -------------- |
tesseract-ocr-spa \ | compose.yaml 安裝 | 容器啟動時自動安裝語言包 | 測試、臨時使用 |
tesseract-ocr-ita \ | 掛載語言包 | 下載 .traineddata 檔案掛載 | 離線環境 |
&& rm -rf /var/lib/apt/lists/* | 自訂 Dockerfile | 建置包含語言包的自訂映像 | 生產環境 |
```
- `tesseract-ocr-spa` — 西班牙文 ### 常用可選語言
- `tesseract-ocr-ita` — 義大利文
### 方法二:掛載語言包 | Tesseract 包名 | 語言 |
| ------------------- | -------- |
| `tesseract-ocr-spa` | 西班牙文 |
| `tesseract-ocr-ita` | 義大利文 |
| `tesseract-ocr-por` | 葡萄牙文 |
| `tesseract-ocr-rus` | 俄文 |
| `tesseract-ocr-ara` | 阿拉伯文 |
| `tesseract-ocr-vie` | 越南文 |
| `tesseract-ocr-tha` | 泰文 |
```yaml > 📖 **詳細配置指南**[OCR 語言擴展指南](../Docker組合配置/OCR語言擴展.md)
volumes: >
- ./tessdata:/usr/share/tesseract-ocr/5/tessdata > 包含完整的 compose.yaml 範例、語言包下載方法、以及 50+ 種可用語言列表。
```
下載語言包https://github.com/tesseract-ocr/tessdata_best
--- ---
## 可選語言包 ## OCRmyPDF vs 翻譯引擎語言支援
| 區域 | 語言 | | 功能 | OCRmyPDF | PDFMathTranslate / BabelDOC |
| ------ | ------------------------------ | | -------- | --------------------------- | --------------------------- |
| 西歐 | 西班牙文、義大利文、葡萄牙文 | | 內建語言 | 8 種 | 15 種 |
| 北歐 | 瑞典文、丹麥文、挪威文、芬蘭文 | | 可擴展 | ✅ 可透過 compose.yaml 添加 | ❌ 固定 |
| 東歐 | 俄文、波蘭文、捷克文、匈牙利文 | | 語言用途 | OCR 文字辨識 | 翻譯目標語言 |
| 中東 | 阿拉伯文、希伯來文、土耳其文 |
| 南亞 | 印地文、孟加拉文、泰米爾文 | **OCRmyPDF 內建語言**:英文、繁體中文、簡體中文、日文、韓文、德文、法文 + 自動多語言模式
| 東南亞 | 泰文、越南文、印尼文 |
**翻譯引擎內建語言**:英文、簡體中文、繁體中文、日文、韓文、德文、法文、西班牙文、義大利文、葡萄牙文、俄文、阿拉伯文、印地文、越南文、泰文
> 💡 如果需要 OCR 更多語言,請參考 [OCR 語言擴展指南](../Docker組合配置/OCR語言擴展.md)。
--- ---
@ -173,4 +179,5 @@ volumes:
- [支援的轉換器](轉換器.md) - [支援的轉換器](轉換器.md)
- [翻譯功能](翻譯功能.md) - [翻譯功能](翻譯功能.md)
- [OCR 語言擴展指南](../Docker組合配置/OCR語言擴展.md)
- [Docker 部署](../部署指南/Docker部署.md) - [Docker 部署](../部署指南/Docker部署.md)

View file

@ -75,6 +75,17 @@ ConvertX-CN 內建兩個 PDF 翻譯引擎:
> 💡 BabelDOC 額外支援 `md-<lang>``html-<lang>` 格式 > 💡 BabelDOC 額外支援 `md-<lang>``html-<lang>` 格式
### 與 OCRmyPDF 語言差異
| 功能 | 翻譯引擎 | OCRmyPDF |
| -------- | ------------ | -------------- |
| 支援語言 | 15 種 | 8 種(可擴展) |
| 用途 | 翻譯目標語言 | OCR 文字辨識 |
翻譯引擎支援 15 種目標語言,而 OCRmyPDF 預設只內建 8 種 OCR 語言。
如需擴展 OCRmyPDF 語言支援,請參考 [OCR 功能 - 新增語言](OCR.md#新增語言)。
--- ---
## 輸出格式 ## 輸出格式

View file

@ -0,0 +1,96 @@
# ==============================================================================
# ConvertX-CN OCR 語言擴展配置
#
# 📚 此配置示範如何在容器啟動時安裝額外的 Tesseract OCR 語言包
#
# 🎯 適用情境:
# - 需要 OCR 辨識西班牙文、義大利文、俄文等額外語言
# - 翻譯引擎支援 15 種語言,但 OCR 預設只內建 8 種
# - 需要處理更多語言的掃描版 PDF
#
# ⚠️ 注意事項:
# - 每次容器啟動時都會安裝語言包,首次啟動較慢
# - 語言包會存在容器內,重啟後需重新安裝
# - 若需永久安裝,請使用自訂 Dockerfile 方式
#
# ==============================================================================
services:
convertx:
image: convertx/convertx-cn:latest
container_name: convertx-cn
restart: unless-stopped
# =========================================================================
# 使用自訂 entrypoint 安裝額外語言包
# =========================================================================
entrypoint: ["/bin/sh", "-c"]
command:
- |
echo "📦 正在安裝額外 OCR 語言包..."
apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr-spa \
tesseract-ocr-ita \
tesseract-ocr-por \
tesseract-ocr-rus \
&& rm -rf /var/lib/apt/lists/*
echo "✅ 語言包安裝完成"
echo "🚀 啟動 ConvertX..."
exec bun run start
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Taipei
- JWT_SECRET=請改成你自己的長隨機字串-至少32個字元
- ACCOUNT_REGISTRATION=true
- HTTP_ALLOWED=true
- ALLOW_UNAUTHENTICATED=false
- AUTO_DELETE_EVERY_N_HOURS=24
# ==============================================================================
# 可用語言包對照表
# ==============================================================================
#
# 💡 只需安裝你需要的語言,不必全部安裝
#
# 語言包名稱 | 語言代碼 | 語言
# ----------------------|---------|----------------
# tesseract-ocr-spa | es | 西班牙文
# tesseract-ocr-ita | it | 義大利文
# tesseract-ocr-por | pt | 葡萄牙文
# tesseract-ocr-rus | ru | 俄文
# tesseract-ocr-ara | ar | 阿拉伯文
# tesseract-ocr-hin | hi | 印地文
# tesseract-ocr-vie | vi | 越南文
# tesseract-ocr-tha | th | 泰文
# tesseract-ocr-swe | sv | 瑞典文
# tesseract-ocr-dan | da | 丹麥文
# tesseract-ocr-nor | no | 挪威文
# tesseract-ocr-fin | fi | 芬蘭文
# tesseract-ocr-pol | pl | 波蘭文
# tesseract-ocr-ces | cs | 捷克文
# tesseract-ocr-hun | hu | 匈牙利文
# tesseract-ocr-tur | tr | 土耳其文
# tesseract-ocr-heb | he | 希伯來文
# tesseract-ocr-ben | bn | 孟加拉文
# tesseract-ocr-tam | ta | 泰米爾文
# tesseract-ocr-ind | id | 印尼文
#
# ==============================================================================
# 內建語言(無需額外安裝)
# ==============================================================================
#
# tesseract-ocr-eng | en | 英文
# tesseract-ocr-chi-tra | zh-TW | 繁體中文
# tesseract-ocr-chi-sim | zh | 簡體中文
# tesseract-ocr-jpn | ja | 日文
# tesseract-ocr-kor | ko | 韓文
# tesseract-ocr-deu | de | 德文
# tesseract-ocr-fra | fr | 法文
#
# ==============================================================================

View file

@ -7,11 +7,12 @@
## 範例檔案 ## 範例檔案
| 檔案 | 用途 | 說明 | | 檔案 | 用途 | 說明 |
| -------------------------- | ---------- | ---------------- | | --------------------------- | ------------ | ----------------- |
| `compose.minimal.yml` | 快速啟動 | 最精簡配置 | | `最小配置.yml` | 快速啟動 | 最精簡配置 |
| `compose.production.yml` | 生產環境 | 包含安全設定 | | `生產環境配置.yml` | 生產環境 | 包含安全設定 |
| `compose.with-traefik.yml` | 反向代理 | Traefik 整合 | | `compose.ocr-languages.yml` | OCR 語言擴展 | 安裝額外 OCR 語言 |
| `nginx.example.conf` | Nginx 設定 | 反向代理設定範例 | | `Traefik配置.yml` | 反向代理 | Traefik 整合 |
| `Nginx範例配置.conf` | Nginx 設定 | 反向代理設定範例 |
--- ---

View file

@ -1,5 +1,5 @@
import { execFile as execFileOriginal } from "node:child_process"; import { execFile as execFileOriginal } from "node:child_process";
import { mkdirSync, existsSync, readdirSync, unlinkSync, rmdirSync, statSync } from "node:fs"; import { mkdirSync, existsSync, readdirSync, unlinkSync, rmdirSync } from "node:fs";
import { join, basename, dirname } from "node:path"; import { join, basename, dirname } from "node:path";
import { ExecFileFn } from "./types"; import { ExecFileFn } from "./types";

View file

@ -1,5 +1,6 @@
import { test, expect, describe, mock } from "bun:test"; import { test, expect, describe, mock } from "bun:test";
import { convert, properties } from "../../src/converters/deark"; import { convert, properties } from "../../src/converters/deark";
import type { ExecFileFn } from "../../src/converters/types";
describe("deark converter", () => { describe("deark converter", () => {
test("properties should have correct structure", () => { test("properties should have correct structure", () => {
@ -9,32 +10,34 @@ describe("deark converter", () => {
expect(properties.outputMode).toBe("archive"); expect(properties.outputMode).toBe("archive");
}); });
test("properties.from should contain archive formats", () => { test("properties.from should contain supported formats", () => {
expect(properties.from.archive).toBeDefined(); expect(properties.from.files).toBeDefined();
expect(properties.from.archive).toContain("zip"); // Archive formats
expect(properties.from.archive).toContain("lha"); expect(properties.from.files).toContain("zip");
expect(properties.from.archive).toContain("arc"); expect(properties.from.files).toContain("lha");
expect(properties.from.files).toContain("arc");
// Image formats
expect(properties.from.files).toContain("ico");
expect(properties.from.files).toContain("bmp");
expect(properties.from.files).toContain("pcx");
}); });
test("properties.from should contain image formats", () => { test("properties.to should have extract option", () => {
expect(properties.from.images).toBeDefined(); expect(properties.to.files).toBeDefined();
expect(properties.from.images).toContain("ico"); expect(properties.to.files).toContain("extract");
expect(properties.from.images).toContain("bmp");
expect(properties.from.images).toContain("pcx");
});
test("properties.to should have extract option for all categories", () => {
for (const category of Object.keys(properties.from)) {
expect(properties.to[category]).toBeDefined();
expect(properties.to[category]).toContain("extract");
}
}); });
test("convert resolves when execFile succeeds", async () => { test("convert resolves when execFile succeeds", async () => {
const mockExecFile = mock((cmd: string, args: string[], callback: Function) => { const mockExecFile = mock(
(
cmd: string,
args: string[],
callback: (error: Error | null, stdout: string, stderr: string) => void,
) => {
// First call is deark, second call is tar // First call is deark, second call is tar
callback(null, "Success", ""); callback(null, "Success", "");
}); },
);
// Mock fs functions would be needed for full test // Mock fs functions would be needed for full test
// This is a simplified test // This is a simplified test
@ -45,7 +48,7 @@ describe("deark converter", () => {
"extract", "extract",
"/tmp/output/test", "/tmp/output/test",
{}, {},
mockExecFile as any, mockExecFile as ExecFileFn,
); );
} catch (error) { } catch (error) {
// Expected to fail due to fs operations not being mocked // Expected to fail due to fs operations not being mocked
@ -54,9 +57,15 @@ describe("deark converter", () => {
}); });
test("convert rejects when deark fails", async () => { test("convert rejects when deark fails", async () => {
const mockExecFile = mock((cmd: string, args: string[], callback: Function) => { const mockExecFile = mock(
(
cmd: string,
args: string[],
callback: (error: Error | null, stdout: string, stderr: string) => void,
) => {
callback(new Error("deark failed"), "", "Unknown or unsupported format"); callback(new Error("deark failed"), "", "Unknown or unsupported format");
}); },
);
try { try {
await convert( await convert(
@ -65,7 +74,7 @@ describe("deark converter", () => {
"extract", "extract",
"/tmp/output/test", "/tmp/output/test",
{}, {},
mockExecFile as any, mockExecFile as ExecFileFn,
); );
expect(true).toBe(false); // Should not reach here expect(true).toBe(false); // Should not reach here
} catch (error) { } catch (error) {

View file

@ -41,7 +41,25 @@ describe("FFmpeg 格式", () => {
const toFormats = ffmpegProps.to.muxer; const toFormats = ffmpegProps.to.muxer;
// 關鍵影片格式wmv 由 asf 處理) // 關鍵影片格式wmv 由 asf 處理)
const keyVideoFormats = ["264", "265", "3g2", "3gp", "asf", "av1", "avi", "flv", "h264", "h265", "hevc", "mkv", "mov", "mp4", "mpeg", "mpg", "webm"]; const keyVideoFormats = [
"264",
"265",
"3g2",
"3gp",
"asf",
"av1",
"avi",
"flv",
"h264",
"h265",
"hevc",
"mkv",
"mov",
"mp4",
"mpeg",
"mpg",
"webm",
];
// 關鍵音訊格式 // 關鍵音訊格式
const keyAudioFormats = ["aac", "ac3", "aiff", "flac", "m4a", "mp3", "ogg", "opus", "wav", "wma"]; const keyAudioFormats = ["aac", "ac3", "aiff", "flac", "m4a", "mp3", "ogg", "opus", "wav", "wma"];
// 關鍵字幕格式 // 關鍵字幕格式
@ -110,7 +128,21 @@ describe("ImageMagick 格式", () => {
// 關鍵 RAW 相機格式 // 關鍵 RAW 相機格式
const keyRawFormats = ["arw", "cr2", "cr3", "dng", "nef", "orf", "raf"]; const keyRawFormats = ["arw", "cr2", "cr3", "dng", "nef", "orf", "raf"];
// 關鍵常見圖片格式 // 關鍵常見圖片格式
const keyImageFormats = ["apng", "avif", "bmp", "gif", "heic", "heif", "ico", "jpeg", "jpg", "jxl", "png", "tiff", "webp"]; const keyImageFormats = [
"apng",
"avif",
"bmp",
"gif",
"heic",
"heif",
"ico",
"jpeg",
"jpg",
"jxl",
"png",
"tiff",
"webp",
];
// 關鍵向量格式 // 關鍵向量格式
const keyVectorFormats = ["eps", "pdf", "ps", "psd", "svg"]; const keyVectorFormats = ["eps", "pdf", "ps", "psd", "svg"];
@ -913,8 +945,16 @@ describe("PDFMathTranslate 格式", () => {
const toFormats = pdfmathtranslateProps.to.document; const toFormats = pdfmathtranslateProps.to.document;
const keyOutputFormats = [ const keyOutputFormats = [
"pdf-en", "pdf-zh", "pdf-zh-TW", "pdf-ja", "pdf-ko", "pdf-en",
"pdf-de", "pdf-fr", "pdf-es", "pdf-it", "pdf-ru" "pdf-zh",
"pdf-zh-TW",
"pdf-ja",
"pdf-ko",
"pdf-de",
"pdf-fr",
"pdf-es",
"pdf-it",
"pdf-ru",
]; ];
test("輸入格式應為 pdf", () => { test("輸入格式應為 pdf", () => {
@ -1011,7 +1051,13 @@ describe("OCRmyPDF 格式", () => {
const toFormats = ocrmypdfProps.to.document; const toFormats = ocrmypdfProps.to.document;
const keyOutputFormats = [ const keyOutputFormats = [
"pdf-en", "pdf-zh-TW", "pdf-zh", "pdf-ja", "pdf-ko", "pdf-de", "pdf-fr" "pdf-en",
"pdf-zh-TW",
"pdf-zh",
"pdf-ja",
"pdf-ko",
"pdf-de",
"pdf-fr",
]; ];
test("輸入格式應為 pdf", () => { test("輸入格式應為 pdf", () => {
@ -1029,8 +1075,8 @@ describe("OCRmyPDF 格式", () => {
console.log(`OCRmyPDF 輸入格式:${fromFormats.length}`); console.log(`OCRmyPDF 輸入格式:${fromFormats.length}`);
}); });
test("輸出格式數量應等於 7 種", () => { test("輸出格式數量應等於 8 種", () => {
expect(toFormats.length).toBe(7); expect(toFormats.length).toBe(8);
console.log(`OCRmyPDF 輸出格式:${toFormats.length}`); console.log(`OCRmyPDF 輸出格式:${toFormats.length}`);
}); });
@ -1081,14 +1127,38 @@ describe("格式總覽", () => {
const summary = [ const summary = [
{ name: "FFmpeg", from: ffmpegProps.from.muxer.length, to: ffmpegProps.to.muxer.length }, { name: "FFmpeg", from: ffmpegProps.from.muxer.length, to: ffmpegProps.to.muxer.length },
{ name: "ImageMagick", from: imagemagickProps.from.images.length, to: imagemagickProps.to.images.length }, {
{ name: "GraphicsMagick", from: graphicsmagickProps.from.image.length, to: graphicsmagickProps.to.image.length }, name: "ImageMagick",
from: imagemagickProps.from.images.length,
to: imagemagickProps.to.images.length,
},
{
name: "GraphicsMagick",
from: graphicsmagickProps.from.image.length,
to: graphicsmagickProps.to.image.length,
},
{ name: "Vips", from: vipsProps.from.images.length, to: vipsProps.to.images.length }, { name: "Vips", from: vipsProps.from.images.length, to: vipsProps.to.images.length },
{ name: "LibreOffice", from: libreofficeProps.from.text.length, to: libreofficeProps.to.text.length }, {
name: "LibreOffice",
from: libreofficeProps.from.text.length,
to: libreofficeProps.to.text.length,
},
{ name: "Pandoc", from: pandocProps.from.text.length, to: pandocProps.to.text.length }, { name: "Pandoc", from: pandocProps.from.text.length, to: pandocProps.to.text.length },
{ name: "Calibre", from: calibreProps.from.document.length, to: calibreProps.to.document.length }, {
{ name: "Inkscape", from: inkscapeProps.from.images.length, to: inkscapeProps.to.images.length }, name: "Calibre",
{ name: "libjxl", from: libjxlProps.from.jxl.length + libjxlProps.from.images.length, to: libjxlProps.to.jxl.length + libjxlProps.to.images.length }, from: calibreProps.from.document.length,
to: calibreProps.to.document.length,
},
{
name: "Inkscape",
from: inkscapeProps.from.images.length,
to: inkscapeProps.to.images.length,
},
{
name: "libjxl",
from: libjxlProps.from.jxl.length + libjxlProps.from.images.length,
to: libjxlProps.to.jxl.length + libjxlProps.to.images.length,
},
{ name: "libheif", from: libheifProps.from.images.length, to: libheifProps.to.images.length }, { name: "libheif", from: libheifProps.from.images.length, to: libheifProps.to.images.length },
{ name: "Assimp", from: assimpProps.from.object.length, to: assimpProps.to.object.length }, { name: "Assimp", from: assimpProps.from.object.length, to: assimpProps.to.object.length },
{ name: "Potrace", from: potraceProps.from.images.length, to: potraceProps.to.images.length }, { name: "Potrace", from: potraceProps.from.images.length, to: potraceProps.to.images.length },
@ -1097,13 +1167,37 @@ describe("格式總覽", () => {
{ name: "XeLaTeX", from: xelatexProps.from.text.length, to: xelatexProps.to.text.length }, { name: "XeLaTeX", from: xelatexProps.from.text.length, to: xelatexProps.to.text.length },
{ name: "dvisvgm", from: dvisvgmProps.from.images.length, to: dvisvgmProps.to.images.length }, { name: "dvisvgm", from: dvisvgmProps.from.images.length, to: dvisvgmProps.to.images.length },
{ name: "Dasel", from: daselProps.from.document.length, to: daselProps.to.document.length }, { name: "Dasel", from: daselProps.from.document.length, to: daselProps.to.document.length },
{ name: "msgconvert", from: msgconvertProps.from.email.length, to: msgconvertProps.to.email.length }, {
name: "msgconvert",
from: msgconvertProps.from.email.length,
to: msgconvertProps.to.email.length,
},
{ name: "VCF", from: vcfProps.from.contacts.length, to: vcfProps.to.contacts.length }, { name: "VCF", from: vcfProps.from.contacts.length, to: vcfProps.to.contacts.length },
{ name: "Markitdown", from: markitdownProps.from.document.length, to: markitdownProps.to.document.length }, {
{ name: "MinerU", from: mineruProps.from.document.length, to: mineruProps.to.document.length }, name: "Markitdown",
{ name: "PDFMathTranslate", from: pdfmathtranslateProps.from.document.length, to: pdfmathtranslateProps.to.document.length }, from: markitdownProps.from.document.length,
{ name: "BabelDOC", from: babeldocProps.from.document.length, to: babeldocProps.to.document.length }, to: markitdownProps.to.document.length,
{ name: "OCRmyPDF", from: ocrmypdfProps.from.document.length, to: ocrmypdfProps.to.document.length }, },
{
name: "MinerU",
from: mineruProps.from.document.length,
to: mineruProps.to.document.length,
},
{
name: "PDFMathTranslate",
from: pdfmathtranslateProps.from.document.length,
to: pdfmathtranslateProps.to.document.length,
},
{
name: "BabelDOC",
from: babeldocProps.from.document.length,
to: babeldocProps.to.document.length,
},
{
name: "OCRmyPDF",
from: ocrmypdfProps.from.document.length,
to: ocrmypdfProps.to.document.length,
},
]; ];
let totalFrom = 0; let totalFrom = 0;