dsh-file-convert 0.4.0
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/LICENSE +28 -0
- package/README.md +225 -0
- package/README.zh-CN.md +203 -0
- package/cordis.patch.yml +3 -0
- package/lib/config.d.ts +36 -0
- package/lib/config.js +17 -0
- package/lib/core/binaries/cache.d.ts +13 -0
- package/lib/core/binaries/cache.js +137 -0
- package/lib/core/binaries/download.d.ts +21 -0
- package/lib/core/binaries/download.js +140 -0
- package/lib/core/binary.d.ts +7 -0
- package/lib/core/binary.js +29 -0
- package/lib/core/converters/data.d.ts +14 -0
- package/lib/core/converters/data.js +145 -0
- package/lib/core/converters/image.d.ts +12 -0
- package/lib/core/converters/image.js +64 -0
- package/lib/core/converters/media.d.ts +21 -0
- package/lib/core/converters/media.js +135 -0
- package/lib/core/converters/office.d.ts +33 -0
- package/lib/core/converters/office.js +206 -0
- package/lib/core/converters/pdf-env.d.ts +1 -0
- package/lib/core/converters/pdf-env.js +8 -0
- package/lib/core/converters/pdf.d.ts +22 -0
- package/lib/core/converters/pdf.js +316 -0
- package/lib/core/detect.d.ts +16 -0
- package/lib/core/detect.js +121 -0
- package/lib/core/errors.d.ts +5 -0
- package/lib/core/errors.js +16 -0
- package/lib/core/formats.d.ts +11 -0
- package/lib/core/formats.js +60 -0
- package/lib/core/index.d.ts +25 -0
- package/lib/core/index.js +52 -0
- package/lib/core/inspect.d.ts +13 -0
- package/lib/core/inspect.js +134 -0
- package/lib/core/ocr.d.ts +38 -0
- package/lib/core/ocr.js +152 -0
- package/lib/core/optimizers.d.ts +29 -0
- package/lib/core/optimizers.js +275 -0
- package/lib/core/paths.d.ts +11 -0
- package/lib/core/paths.js +19 -0
- package/lib/core/router.d.ts +64 -0
- package/lib/core/router.js +288 -0
- package/lib/core/types.d.ts +199 -0
- package/lib/core/types.js +8 -0
- package/lib/core/utils/exec.d.ts +38 -0
- package/lib/core/utils/exec.js +89 -0
- package/lib/core/utils/pages.d.ts +12 -0
- package/lib/core/utils/pages.js +46 -0
- package/lib/format.d.ts +23 -0
- package/lib/format.js +84 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +51 -0
- package/lib/tools/batch-convert.d.ts +5 -0
- package/lib/tools/batch-convert.js +155 -0
- package/lib/tools/convert-file.d.ts +3 -0
- package/lib/tools/convert-file.js +57 -0
- package/lib/tools/inspect-file.d.ts +3 -0
- package/lib/tools/inspect-file.js +29 -0
- package/lib/tools/install-media.d.ts +9 -0
- package/lib/tools/install-media.js +55 -0
- package/lib/tools/install-ocr.d.ts +7 -0
- package/lib/tools/install-ocr.js +43 -0
- package/lib/tools/list-conversions.d.ts +2 -0
- package/lib/tools/list-conversions.js +18 -0
- package/lib/tools/optimize-file.d.ts +3 -0
- package/lib/tools/optimize-file.js +91 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-file-convert contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Third-party notice: dsh-file-convert does not distribute any external binaries.
|
|
26
|
+
It relies on npm-shipped libraries (sharp, pdfjs-dist, @napi-rs/canvas) and,
|
|
27
|
+
in future versions, on separately installed tools (FFmpeg, LibreOffice,
|
|
28
|
+
Poppler), each licensed under its own terms.
|
package/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# dsh-file-convert
|
|
2
|
+
|
|
3
|
+
**Local-first file conversion for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).**
|
|
4
|
+
|
|
5
|
+
Convert images, PDFs and data files directly inside your DSH agent sessions — no API keys, no uploads, no servers, no token cost for the conversion itself. Files never leave your machine.
|
|
6
|
+
|
|
7
|
+
> **Unofficial community plugin.** Not affiliated with or endorsed by DeepSeek.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Agents constantly need file conversions: "turn this PDF into images", "give me that JSON as YAML", "convert all JPGs in this folder to WebP". Instead of shelling out or pasting data around, `dsh-file-convert` gives your agent seven purpose-built tools backed by battle-tested local libraries.
|
|
12
|
+
|
|
13
|
+
- ✅ **Local execution** — files never leave the machine
|
|
14
|
+
- ✅ **No API key, no server, no conversion tokens**
|
|
15
|
+
- ✅ **Natural language friendly** — the agent calls the tools, you just ask
|
|
16
|
+
- ✅ **Batch conversions** with a compact summary
|
|
17
|
+
- ✅ **Honest failures** — missing dependency, unsupported pair, existing output: every error says exactly why
|
|
18
|
+
- ✅ **Images, PDF and data need zero external binaries** — prebuilt npm packages only. Media needs one tool (ffmpeg), clearly reported when missing
|
|
19
|
+
|
|
20
|
+
## Supported conversions
|
|
21
|
+
|
|
22
|
+
| Source | Targets |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| PNG, JPG, WEBP | PNG, JPG, WEBP (any-to-any) |
|
|
25
|
+
| SVG | PNG, JPG, WEBP |
|
|
26
|
+
| PDF | PNG, JPG, TXT |
|
|
27
|
+
| JSON | YAML, CSV |
|
|
28
|
+
| YAML | JSON, CSV |
|
|
29
|
+
| DOCX, PPTX, XLSX | PDF |
|
|
30
|
+
| PDF | PNG, JPG, TXT, DOCX (experimental); TXT supports OCR for scanned PDFs |
|
|
31
|
+
| MP4 | GIF, MP3 |
|
|
32
|
+
| MOV | MP4 |
|
|
33
|
+
| WAV | MP3 |
|
|
34
|
+
| CSV | JSON, YAML |
|
|
35
|
+
|
|
36
|
+
26 conversions. Images, PDF and data work out of the box via `npm install`. Optional tools unlock the rest, each clearly reported by `list_conversions` when missing:
|
|
37
|
+
|
|
38
|
+
- **FFmpeg** -> media rows. Install it system-wide (preferred for untrusted media), **or ask the agent to run `install_media_dependencies`** — it downloads pinned current builds (FFmpeg 6.1.1, ~56 MB total, one time) into the plugin cache, sha256-verified, from the npmmirror binary CDN with the GitHub release as fallback.
|
|
39
|
+
- **LibreOffice** -> DOCX/PPTX/XLSX to PDF. `winget install TheDocumentFoundation.LibreOffice` / `brew install --cask libreoffice` / `apt install libreoffice`.
|
|
40
|
+
- **Ghostscript** -> PDF compression in `optimize_file`.
|
|
41
|
+
- **Python + pdf2docx** -> the experimental PDF to DOCX row (`pip install pdf2docx`).
|
|
42
|
+
- **Tesseract** (optional) -> faster OCR for scanned PDFs; without it the bundled tesseract.js is used and its language data is fetched explicitly via `install_ocr_dependencies` (`winget install UB-Mannheim.TesseractOCR`).
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
Inside a DSH profile — three ways, easiest first:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
# 1. from npm (once published)
|
|
50
|
+
dsh plugin --profile default add dsh-file-convert
|
|
51
|
+
|
|
52
|
+
# 2. straight from GitHub (a `prepare` build runs; allow it once)
|
|
53
|
+
dsh plugin --profile default add github:zzy-12345678/dsh-file-convert
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Git installs may ask you to allow the build step in the profile's `pnpm-workspace.yaml`:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
allowBuilds:
|
|
60
|
+
dsh-file-convert: true
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
From a local checkout (e.g. while hacking on it):
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
git clone https://github.com/zzy-12345678/dsh-file-convert
|
|
67
|
+
cd dsh-file-convert && npm install && npm run build
|
|
68
|
+
dsh plugin --profile default add /absolute/path/to/dsh-file-convert
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then restart DSH (`dsh web` or your usual entry point). All seven tools appear automatically.
|
|
72
|
+
|
|
73
|
+
## The seven tools
|
|
74
|
+
|
|
75
|
+
### `convert_file`
|
|
76
|
+
|
|
77
|
+
Convert one file.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{ "input": "/tmp/report.pdf", "output_format": "png", "dpi": 200 }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Converted: /tmp/report.pdf (pdf) -> /tmp/report.png (png)
|
|
85
|
+
1.2 MB -> 431.0 KB in 1.4s
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- Default output: next to the input file, same base name, new extension.
|
|
89
|
+
- Multi-page PDFs produce `<name>-<page>.<ext>` for every page; `pages: "1-3,5"` selects pages (outputs keep their real page numbers, text joins only the selection).
|
|
90
|
+
- Scanned PDFs → TXT: `ocr: true` (optionally `ocr_lang`, default `chi_sim+eng`) recognizes the rendered pages instead of the text layer. Engine priority: a local Tesseract CLI, then the bundled tesseract.js (whose language data is never downloaded implicitly — run `install_ocr_dependencies` first, about 10-30 MB per language).
|
|
91
|
+
- Existing outputs are refused unless `overwrite: true`.
|
|
92
|
+
- Options: `output`, `overwrite`, `quality` (1–100), `dpi` (PDF/SVG rasterization), `pages`, `ocr`, `ocr_lang`.
|
|
93
|
+
|
|
94
|
+
### `batch_convert`
|
|
95
|
+
|
|
96
|
+
Convert every matching file in a directory (top level).
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{ "input_dir": "/home/me/Pictures", "output_format": "webp" }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Batch convert in /home/me/Pictures -> WEBP
|
|
104
|
+
Converted: 18, skipped: 2, failed: 0
|
|
105
|
+
Output dir: /home/me/Pictures/output
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `input_format` filters by source format; omit it to auto-detect every convertible file.
|
|
109
|
+
- `output_dir` defaults to `<input_dir>/output`.
|
|
110
|
+
- Existing outputs are **skipped** (not overwritten) unless `overwrite: true`.
|
|
111
|
+
|
|
112
|
+
### `inspect_file`
|
|
113
|
+
|
|
114
|
+
Facts before action, detected from file content — not just the extension:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{ "input": "/tmp/scan.pdf" }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{ "kind": "pdf", "pages": 24, "encrypted": false, "likelyScanned": true, "bytes": 13000000 }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `optimize_file`
|
|
125
|
+
|
|
126
|
+
Shrink a file toward a target size instead of converting it:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{ "input": "video.mp4", "target_size_mb": 20 }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Optimized: video.mp4 (mp4) -> video-min.mp4
|
|
134
|
+
18.3 MB -> 19.7 MB (target 20 MB) in 41.2s
|
|
135
|
+
Applied: two-pass x264: video 512k + audio 128k over 185.0s
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- MP4/MOV: two-pass x264, the video bitrate is computed from the target (audio 128k, dropping to 64k for tight targets); output is always MP4. Requires ffmpeg + ffprobe.
|
|
139
|
+
- JPG/WEBP: binary-searches the highest encoder quality that fits; PNG uses palette reduction. No external tools needed.
|
|
140
|
+
- PDF: Ghostscript quality presets (printer/ebook/screen), first preset that fits the target wins; requires Ghostscript.
|
|
141
|
+
- Targets below what the codec can physically reach are refused with the achievable minimum.
|
|
142
|
+
- GIF optimization is not supported yet.
|
|
143
|
+
|
|
144
|
+
### `install_media_dependencies`
|
|
145
|
+
|
|
146
|
+
One-call media setup: downloads pinned FFmpeg 6.1.1 static builds (ffmpeg + ffprobe) into the plugin cache (`~/.dsh-file-convert/bin`), verifies the pinned sha256, and proves the binaries run before reporting success. Served from the npmmirror binary CDN with the GitHub release as a byte-identical fallback. System installs keep priority over the cache. Ask the user for consent first — it is a sizable download.
|
|
147
|
+
|
|
148
|
+
### `install_ocr_dependencies`
|
|
149
|
+
|
|
150
|
+
Downloads the tesseract.js language data (about 10-30 MB per language, `chi_sim+eng` by default) into the plugin cache, so `ocr: true` works without a local Tesseract. Skips when a local Tesseract CLI is installed or the data is already cached. Ask the user for consent first — conversions never download language data implicitly.
|
|
151
|
+
|
|
152
|
+
### `list_conversions`
|
|
153
|
+
|
|
154
|
+
All 26 conversions with their live availability on *this* machine — unavailable rows name the missing tool and how to install it. Images, PDF and data rows are usable out of the box; media, office and PDF-compression rows depend on the optional tools (media can even be set up by the agent via `install_media_dependencies`).
|
|
155
|
+
|
|
156
|
+
## Plugin config
|
|
157
|
+
|
|
158
|
+
| Key | Default | Meaning |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| `quality` | `85` | Default JPEG/WebP quality (1–100) |
|
|
161
|
+
| `dpi` | `150` | Default rasterization DPI for PDF inputs |
|
|
162
|
+
| `timeoutMs` | `120000` | Cooperative timeout for one conversion |
|
|
163
|
+
| `maxInputMb` | `2048` | Refuse inputs above this size (MB) |
|
|
164
|
+
| `maxPdfPages` | `200` | Full-document PDF rasterization refuses more pages; use `pages` for larger documents |
|
|
165
|
+
| `maxOutputPixels` | `16000000` | Clamp rasterized pixels per page (width × height) to this budget |
|
|
166
|
+
| `batchMaxFiles` | `500` | Max files examined per `batch_convert` run; beyond it the summary reports what was skipped instead of silently capping |
|
|
167
|
+
| `outputRoots` | `[]` | When non-empty, explicit `output` paths must resolve inside one of these directories (recommended for shared deployments; the default next-to-input output is always exempt) |
|
|
168
|
+
| `ffmpegPath` / `ffprobePath` | - | Explicit binary paths when ffmpeg is not on PATH (common on Windows) |
|
|
169
|
+
| `sofficePath` / `ghostscriptPath` / `pythonPath` / `tesseractPath` | - | Explicit paths for the optional tools, overriding auto-detection |
|
|
170
|
+
|
|
171
|
+
## Architecture
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
DSH
|
|
175
|
+
│
|
|
176
|
+
dsh-file-convert
|
|
177
|
+
┌──────────────┴──────────────┐
|
|
178
|
+
src/index.ts src/core/ ← the whole engine,
|
|
179
|
+
(thin DSH glue: (no DSH imports) testable standalone
|
|
180
|
+
name/inject/apply, │
|
|
181
|
+
Config schema, ConversionRouter
|
|
182
|
+
7 tool registrations) │
|
|
183
|
+
┌─────────────────┼─────────────────┐
|
|
184
|
+
↓ ↓ ↓
|
|
185
|
+
ImageConverter PdfConverter DataConverter
|
|
186
|
+
sharp pdfjs-dist js-yaml
|
|
187
|
+
(libvips npm) @napi-rs/canvas csv-parse / stringify
|
|
188
|
+
MediaConverter OfficeConverter
|
|
189
|
+
ffmpeg (detected) LibreOffice (detected)
|
|
190
|
+
PdfToDocxConverter optimize_file/pdf
|
|
191
|
+
python + pdf2docx Ghostscript (detected)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **Declarative matrix**: every conversion is a data row (`{ from, to }`) on its converter. Routing, `list_conversions` and dependency checks are all derived from it.
|
|
195
|
+
- **Detection**: content first - binary magic (file-type), SVG sniffing, and JSON parsing (plus a YAML document-marker guess for extension-less files) - with the extension as fallback. Conflicts resolve in favor of the content, with a warning.
|
|
196
|
+
- **Core is DSH-agnostic**: `src/core` never imports Cordis/DSH, so the engine can be unit-tested, wrapped in a CLI, or served over MCP later. If the DSH developer-preview API shifts, only the glue layer changes.
|
|
197
|
+
- **Dependencies**: external binaries (FFmpeg, LibreOffice, Poppler) are *detected, never auto-installed* — `list_conversions` reports them and prints per-platform install hints. The interface is already in place (`BinaryDependency`).
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
npm install
|
|
203
|
+
npm run build # tsc -> lib/
|
|
204
|
+
npm test # vitest, 31 tests
|
|
205
|
+
npm run smoke # end-to-end against lib/
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Add a conversion = add one capability row + implement it in a converter. Add a backend = implement the `Converter` interface and register it in `createRouter()`.
|
|
209
|
+
|
|
210
|
+
## Fidelity & safety expectations
|
|
211
|
+
|
|
212
|
+
- **Lossy by nature**: PDF→DOCX (experimental), OCR and office→PDF are reconstructions — expect layout and recognition differences. `inspect_file`'s `likelyScanned` flag tells you when OCR is the right tool, and results carry warnings.
|
|
213
|
+
- **Cached ffmpeg builds**: the download convenience installs pinned sha256-verified FFmpeg 6.1.1 static builds. For untrusted media, a current system FFmpeg takes priority — prefer it in security-sensitive setups.
|
|
214
|
+
- **Not a sandbox**: `outputRoots` resolves symlinks and resource limits (`maxInputMb`, `maxPdfPages`, `maxOutputPixels`, `batchMaxFiles`) cap runaway jobs, but the default next-to-input output is intentionally exempt from roots, and an agent that may write files can always write somewhere. For hostile multi-tenant use, add OS-level isolation on top.
|
|
215
|
+
|
|
216
|
+
## Roadmap
|
|
217
|
+
|
|
218
|
+
- ~~V0.2 — Media (FFmpeg)~~ **shipped**: MP4→GIF/MP3, WAV→MP3, MOV→MP4, plus `optimize_file` with target-size two-pass encoding.
|
|
219
|
+
- ~~V0.3 — Office + PDF tooling~~ **shipped**: DOCX/PPTX/XLSX→PDF via LibreOffice, experimental PDF→DOCX via python pdf2docx, PDF compression via Ghostscript; on-demand dependency downloads with an automatic CN mirror.
|
|
220
|
+
- ~~V0.4 — OCR (Tesseract)~~ **shipped**: PDF→TXT supports `ocr: true` (optional `ocr_lang`), local Tesseract CLI first with the bundled tesseract.js as fallback, language data fetched explicitly via `install_ocr_dependencies`.
|
|
221
|
+
- Later: OCR → DOCX for scanned PDFs, conversion chains (PPTX→PDF→PNG), video downscaling in `optimize_file`, resize/rotate image options.
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
[MIT](./LICENSE). dsh-file-convert distributes no external binaries; runtime libraries (sharp, pdfjs-dist, @napi-rs/canvas, js-yaml, csv) are installed from npm under their own licenses.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# dsh-file-convert
|
|
2
|
+
|
|
3
|
+
**为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 打造的本地优先文件转换插件。**
|
|
4
|
+
|
|
5
|
+
在 DSH Agent 会话里直接转换图片、PDF 和数据文件——不需要 API Key,不上传文件,不需要服务器,转换本身零 Token 消耗,文件永远不离开你的电脑。
|
|
6
|
+
|
|
7
|
+
> **非官方社区插件。** 与 DeepSeek 官方无隶属关系。
|
|
8
|
+
|
|
9
|
+
## 为什么做这个
|
|
10
|
+
|
|
11
|
+
Agent 日常需要各种格式转换:"把这个 PDF 转成图片"、"把这个 JSON 变成 YAML"、"把文件夹里所有 JPG 转成 WebP"。`dsh-file-convert` 提供七个专用 tool,背后是久经考验的本地库,你只需用自然语言提需求。
|
|
12
|
+
|
|
13
|
+
- ✅ **本地执行** —— 文件不出机器
|
|
14
|
+
- ✅ **不要 API Key、不要服务器、不耗转换 Token**
|
|
15
|
+
- ✅ **自然语言友好** —— Agent 调 tool,你直接说话
|
|
16
|
+
- ✅ **批量转换**,返回紧凑的结果摘要
|
|
17
|
+
- ✅ **失败必有原因** —— 缺依赖 / 不支持的组合 / 输出已存在,每个错误都说明清楚
|
|
18
|
+
- ✅ **图片/PDF/数据零外部依赖** —— npm 预编译包装完即用;音视频只依赖 ffmpeg,缺了会明确提示
|
|
19
|
+
|
|
20
|
+
## 当前支持的转换
|
|
21
|
+
|
|
22
|
+
| 输入 | 可输出 |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| PNG、JPG、WEBP | PNG、JPG、WEBP(任意互转) |
|
|
25
|
+
| SVG | PNG、JPG、WEBP |
|
|
26
|
+
| PDF | PNG、JPG、TXT |
|
|
27
|
+
| JSON | YAML、CSV |
|
|
28
|
+
| YAML | JSON、CSV |
|
|
29
|
+
| DOCX、PPTX、XLSX | PDF |
|
|
30
|
+
| PDF | PNG、JPG、TXT、DOCX(实验性);TXT 支持扫描件 OCR |
|
|
31
|
+
| MP4 | GIF、MP3 |
|
|
32
|
+
| MOV | MP4 |
|
|
33
|
+
| WAV | MP3 |
|
|
34
|
+
| CSV | JSON、YAML |
|
|
35
|
+
|
|
36
|
+
共 26 种转换。图片/PDF/数据 `npm install` 后开箱即用;其余按"装一个工具解锁一类"的节奏,缺什么 `list_conversions` 都会明说:
|
|
37
|
+
|
|
38
|
+
- **FFmpeg** -> 音视频四条。装系统级(处理不可信媒体时优先),**或让 Agent 跑 `install_media_dependencies`**——下载固定的现代构建(FFmpeg 6.1.1,约 56 MB,一次性)到插件缓存,sha256 校验,npmmirror 二进制 CDN 优先、GitHub release 自动回退。
|
|
39
|
+
- **LibreOffice** -> DOCX/PPTX/XLSX 转 PDF。`winget install TheDocumentFoundation.LibreOffice` / `brew install --cask libreoffice` / `apt install libreoffice`。
|
|
40
|
+
- **Ghostscript** -> `optimize_file` 的 PDF 压缩。
|
|
41
|
+
- **Python + pdf2docx** -> 实验性的 PDF 转 DOCX(`pip install pdf2docx`)。
|
|
42
|
+
- **Tesseract**(可选)-> 更快的扫描件 OCR;不装则用内置 tesseract.js,其语言数据通过 `install_ocr_dependencies` 显式下载(`winget install UB-Mannheim.TesseractOCR`,记得勾选 chi_sim 语言组件)。
|
|
43
|
+
|
|
44
|
+
## 安装
|
|
45
|
+
|
|
46
|
+
在 DSH profile 中——三种方式,按省事程度排序:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
# 1. 从 npm 安装(发包后可用)
|
|
50
|
+
dsh plugin --profile default add dsh-file-convert
|
|
51
|
+
|
|
52
|
+
# 2. 直接从 GitHub 安装(会执行一次构建,需放行)
|
|
53
|
+
dsh plugin --profile default add github:zzy-12345678/dsh-file-convert
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Git 安装可能需要在 profile 的 `pnpm-workspace.yaml` 里放行构建步骤:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
allowBuilds:
|
|
60
|
+
dsh-file-convert: true
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
本地开发安装:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
git clone https://github.com/zzy-12345678/dsh-file-convert
|
|
67
|
+
cd dsh-file-convert && npm install && npm run build
|
|
68
|
+
dsh plugin --profile default add /absolute/path/to/dsh-file-convert
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
重启 DSH(`dsh web` 或你的常规入口)后,七个 tool 自动出现。
|
|
72
|
+
|
|
73
|
+
## 七个 Tool
|
|
74
|
+
|
|
75
|
+
### `convert_file` —— 单文件转换
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "input": "/tmp/report.pdf", "output_format": "png", "dpi": 200 }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- 默认输出到源文件同目录、同名换后缀。
|
|
82
|
+
- 多页 PDF 会为每一页输出 `<文件名>-<页码>.<后缀>`;`pages: "1-3,5"` 选择页码(输出保留真实页码,TXT 只拼接所选页)。
|
|
83
|
+
- 扫描件 PDF → TXT:`ocr: true`(可选 `ocr_lang`,默认 `chi_sim+eng`)对渲染页面做识别,而不是读文本层。引擎优先本机 Tesseract CLI,其次插件内置的 tesseract.js(语言数据不会随转换隐式下载——先让 Agent 跑 `install_ocr_dependencies`,约每种语言 10-30 MB)。
|
|
84
|
+
- 输出文件已存在时默认拒绝,需显式 `overwrite: true`。
|
|
85
|
+
- 可选参数:`output`、`overwrite`、`quality`(1–100)、`dpi`(PDF/SVG 光栅化)、`pages`、`ocr`、`ocr_lang`。
|
|
86
|
+
|
|
87
|
+
### `batch_convert` —— 目录批量转换
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "input_dir": "/home/me/Pictures", "output_format": "webp" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- `input_format` 可按源格式过滤;不传则自动探测所有可转换文件。
|
|
94
|
+
- `output_dir` 默认为 `<input_dir>/output`。
|
|
95
|
+
- 已存在的输出默认**跳过**(不覆盖),`overwrite: true` 时替换。
|
|
96
|
+
|
|
97
|
+
### `inspect_file` —— 转换前先看清楚
|
|
98
|
+
|
|
99
|
+
基于文件内容(magic bytes)而非扩展名识别格式:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{ "kind": "pdf", "pages": 24, "encrypted": false, "likelyScanned": true, "bytes": 13000000 }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
图片返回尺寸/通道数,数据文件返回记录数。`likelyScanned: true` 提示这可能是扫描件——转 TXT 时加 `ocr: true` 即可识别。
|
|
106
|
+
|
|
107
|
+
### `optimize_file` —— 按目标体积压缩
|
|
108
|
+
|
|
109
|
+
PDF 走 Ghostscript 三档预设(printer/ebook/screen)自动迭代:某档已达标就停,全超则保留最小档并如实告知。需要 Ghostscript(`winget install ArtifexSoftware.GhostScript`)。
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{ "input": "video.mp4", "target_size_mb": 20 }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- MP4/MOV:两遍编码 x264,按目标体积反推视频码率(音频 128k,紧张时降到 64k),输出统一为 MP4,需要 ffmpeg + ffprobe。
|
|
116
|
+
- JPG/WEBP:二分搜索能塞进目标体积的最高编码质量;PNG 走调色板压缩。图片不需要任何外部工具。
|
|
117
|
+
- 目标体积低于编码物理下限时直接拒绝,并给出可达的最低体积。
|
|
118
|
+
- GIF/PDF 压缩暂不支持。
|
|
119
|
+
|
|
120
|
+
### `install_media_dependencies` —— 一键补齐媒体依赖
|
|
121
|
+
|
|
122
|
+
下载固定的 FFmpeg 6.1.1 静态构建(ffmpeg + ffprobe 两个 ~28 MB 文件)到插件缓存(`~/.dsh-file-convert/bin`),校验钉死的 sha256,并真实执行一次二进制确认可用后才报告成功。npmmirror 二进制 CDN 优先,GitHub release 为字节一致的回退源。系统安装的 ffmpeg 优先于缓存。体积可观,调用前请先征得用户同意。
|
|
123
|
+
|
|
124
|
+
默认从 `npmmirror.com` 镜像下载固定版本的 `@ffmpeg-installer` / `@ffprobe-installer` 二进制到插件缓存(`~/.dsh-file-convert/bin`),校验 sha512 完整性,并真实执行一次二进制确认可用后才报告成功(npmjs.org 自动回退,`registry` 参数可指定其它源)。系统安装的 ffmpeg 优先于缓存。体积可观,调用前请先征得用户同意。
|
|
125
|
+
|
|
126
|
+
### `install_ocr_dependencies` —— 一键补齐 OCR 语言包
|
|
127
|
+
|
|
128
|
+
把 tesseract.js 的语言数据(默认 `chi_sim+eng`,约每种语言 10-30 MB)下载进插件缓存(`~/.dsh-file-convert/tessdata`),让 `ocr: true` 在没有本机 Tesseract 时也能工作。已装本机 Tesseract 或语言包已缓存时跳过。体积可观,调用前请先征得用户同意——转换路径绝不隐式下载语言数据。
|
|
129
|
+
|
|
130
|
+
### `list_conversions` —— 当前机器的能力清单
|
|
131
|
+
|
|
132
|
+
列出全部 26 种转换在当前机器上的实时可用状态;某项不可用时,明确指出缺哪个外部工具并给出安装提示。图片/PDF/数据类开箱即用,音视频、Office、PDF 压缩依赖可选工具(媒体依赖还能由 Agent 经 `install_media_dependencies` 自动补齐)。
|
|
133
|
+
|
|
134
|
+
## 插件配置
|
|
135
|
+
|
|
136
|
+
| 键 | 默认值 | 说明 |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `quality` | `85` | JPEG/WebP 默认质量(1–100) |
|
|
139
|
+
| `dpi` | `150` | PDF 光栅化默认 DPI |
|
|
140
|
+
| `timeoutMs` | `120000` | 单次转换的协作式超时(毫秒) |
|
|
141
|
+
| `maxInputMb` | `2048` | 拒绝超过此大小(MB)的输入 |
|
|
142
|
+
| `maxPdfPages` | `200` | 整本 PDF 光栅化超过此页数即拒绝;更大的文档请用 `pages` 选择 |
|
|
143
|
+
| `maxOutputPixels` | `16000000` | 每页光栅化像素(宽 × 高)钳制到此预算 |
|
|
144
|
+
| `batchMaxFiles` | `500` | 每次 `batch_convert` 最多检查的文件数;超出时会在结果里明确报告跳过了多少,而不是静默截断 |
|
|
145
|
+
| `outputRoots` | `[]` | 非空时,显式指定的 `output` 路径必须落在这些目录之内(共享部署建议开启;默认写到输入文件旁的输出不受限) |
|
|
146
|
+
| `ffmpegPath` / `ffprobePath` | - | ffmpeg 不在 PATH 时(Windows 常见)手动指定二进制路径 |
|
|
147
|
+
| `sofficePath` / `ghostscriptPath` / `pythonPath` / `tesseractPath` | - | 各可选工具的手动路径,优先于自动探测 |
|
|
148
|
+
|
|
149
|
+
## 架构
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
DSH
|
|
153
|
+
│
|
|
154
|
+
dsh-file-convert
|
|
155
|
+
┌──────────────┴──────────────┐
|
|
156
|
+
src/index.ts src/core/ ← 整个引擎,
|
|
157
|
+
(薄胶水层: (不 import DSH) 可独立测试
|
|
158
|
+
name/inject/apply、 │
|
|
159
|
+
Config schema、 ConversionRouter
|
|
160
|
+
7 个 tool 注册) │
|
|
161
|
+
┌─────────────────┼─────────────────┐
|
|
162
|
+
↓ ↓ ↓
|
|
163
|
+
ImageConverter PdfConverter DataConverter
|
|
164
|
+
sharp pdfjs-dist js-yaml
|
|
165
|
+
(npm libvips) @napi-rs/canvas csv-parse / stringify
|
|
166
|
+
MediaConverter OfficeConverter
|
|
167
|
+
ffmpeg(自动检测) LibreOffice(自动检测)
|
|
168
|
+
PdfToDocxConverter optimize_file/pdf
|
|
169
|
+
python+pdf2docx Ghostscript(自动检测)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- **声明式转换矩阵**:每条转换是 converter 上的一行数据(`{ from, to }`),Router 路由、`list_conversions`、依赖检查全部由此推导。
|
|
173
|
+
- **格式识别**:内容优先——二进制 magic(file-type)、SVG 嗅探、JSON 解析(无扩展名文件还会尝试 YAML 文档标记猜测)——扩展名兜底;两者冲突以内容为准并给出 warning。
|
|
174
|
+
- **核心与 DSH 解耦**:`src/core` 不依赖 Cordis/DSH,可直接单测、包 CLI、将来包 MCP server。DSH developer preview 的 API 若有破坏性变更,只需改胶水层。
|
|
175
|
+
- **依赖管理**:外部二进制(FFmpeg/LibreOffice/Poppler)只做**检测,绝不自动安装**——`list_conversions` 报告缺失并给出各平台安装提示。接口已就位(`BinaryDependency`)。
|
|
176
|
+
|
|
177
|
+
## 开发
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
npm install
|
|
181
|
+
npm run build # tsc -> lib/
|
|
182
|
+
npm test # vitest,31 个测试
|
|
183
|
+
npm run smoke # 针对 lib/ 的端到端冒烟测试
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
加一种转换 = 在 converter 的能力表里加一行数据并实现它。加一类后端 = 实现 `Converter` 接口并在 `createRouter()` 注册。
|
|
187
|
+
|
|
188
|
+
## 保真度与安全预期
|
|
189
|
+
|
|
190
|
+
- **天然有损**:PDF→DOCX(实验性)、OCR、office→PDF 都是重建——版式和识别误差不可避免。`inspect_file` 的 `likelyScanned` 标记帮你判断何时该用 OCR,结果里会带警告。
|
|
191
|
+
- **缓存版 ffmpeg**:便捷下载安装的是钉死 sha256 的 FFmpeg 6.1.1 静态构建。处理不可信媒体时,新版系统 FFmpeg 优先——安全敏感场景请优先系统安装。
|
|
192
|
+
- **不是沙箱**:`outputRoots` 会解析符号链接,资源上限(`maxInputMb`、`maxPdfPages`、`maxOutputPixels`、`batchMaxFiles`)能拦住失控任务,但默认"写到输入文件旁"的输出有意不受 roots 约束,且能写文件的 Agent 总能找到地方写。敌对多租户场景请在操作系统层再加隔离。
|
|
193
|
+
|
|
194
|
+
## Roadmap
|
|
195
|
+
|
|
196
|
+
- ~~V0.2 —— 音视频(FFmpeg)~~ **已发布**:MP4→GIF/MP3、WAV→MP3、MOV→MP4,以及按目标体积两遍编码的 `optimize_file`。
|
|
197
|
+
- ~~V0.3 —— Office + PDF 工具链~~ **已发布**:LibreOffice 解锁 DOCX/PPTX/XLSX→PDF,python pdf2docx 支持实验性 PDF→DOCX,Ghostscript 支持 PDF 压缩;依赖支持按需下载并默认走国内镜像。
|
|
198
|
+
- ~~V0.4 —— OCR(Tesseract)~~ **已发布**:PDF→TXT 支持 `ocr: true`(可选 `ocr_lang`),Tesseract CLI 优先 + tesseract.js 兜底,语言数据经 `install_ocr_dependencies` 显式下载。
|
|
199
|
+
- 更远:扫描件 OCR → DOCX、转换链(PPTX→PDF→PNG)、`optimize_file` 视频降分辨率、图片缩放/旋转。
|
|
200
|
+
|
|
201
|
+
## 许可
|
|
202
|
+
|
|
203
|
+
[MIT](./LICENSE)。dsh-file-convert 不分发任何外部二进制;运行时依赖库(sharp、pdfjs-dist、@napi-rs/canvas、js-yaml、csv)由用户从 npm 安装,各归其许可。
|
package/cordis.patch.yml
ADDED
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
2
|
+
export interface Config {
|
|
3
|
+
/** Default JPEG/WebP quality (1-100). */
|
|
4
|
+
quality: number;
|
|
5
|
+
/** Default rasterization DPI for pdf/svg inputs. */
|
|
6
|
+
dpi: number;
|
|
7
|
+
/** Cooperative deadline for one conversion, in milliseconds. */
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
/** Max files examined per batch_convert run before it stops and reports. */
|
|
10
|
+
batchMaxFiles: number;
|
|
11
|
+
/**
|
|
12
|
+
* When non-empty, explicit output paths must resolve inside one of these
|
|
13
|
+
* directories. Empty (default) = unrestricted - fine on a personal machine;
|
|
14
|
+
* set it when the harness is shared or exposed.
|
|
15
|
+
*/
|
|
16
|
+
outputRoots: string[];
|
|
17
|
+
/** Explicit path to the ffmpeg binary (Windows installs without PATH). */
|
|
18
|
+
ffmpegPath?: string;
|
|
19
|
+
/** Explicit path to the ffprobe binary (usually next to ffmpeg). */
|
|
20
|
+
ffprobePath?: string;
|
|
21
|
+
/** Explicit path to the LibreOffice soffice binary. */
|
|
22
|
+
sofficePath?: string;
|
|
23
|
+
/** Explicit path to the Ghostscript binary (gswin64c on Windows). */
|
|
24
|
+
ghostscriptPath?: string;
|
|
25
|
+
/** Explicit python interpreter for the pdf2docx-based PDF → DOCX conversion. */
|
|
26
|
+
pythonPath?: string;
|
|
27
|
+
/** Explicit tesseract binary for OCR (falls back to bundled tesseract.js). */
|
|
28
|
+
tesseractPath?: string;
|
|
29
|
+
/** Refuse inputs above this size (MB). */
|
|
30
|
+
maxInputMb: number;
|
|
31
|
+
/** Refuse full-document PDF rasterization above this page count. */
|
|
32
|
+
maxPdfPages: number;
|
|
33
|
+
/** Clamp rasterized pixels per page (width x height) to this budget. */
|
|
34
|
+
maxOutputPixels: number;
|
|
35
|
+
}
|
|
36
|
+
export declare const Config: Schema<Config>;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
2
|
+
export const Config = Schema.object({
|
|
3
|
+
quality: Schema.number().min(1).max(100).default(85).description('Default JPEG/WebP quality (1-100).'),
|
|
4
|
+
dpi: Schema.number().min(72).max(600).default(150).description('Default rasterization DPI for PDF/SVG inputs.'),
|
|
5
|
+
timeoutMs: Schema.number().min(1000).default(120_000).description('Cooperative timeout for one conversion (ms). For long video jobs consider 600000.'),
|
|
6
|
+
batchMaxFiles: Schema.number().min(1).default(500).description('Max files examined per batch_convert run; extra files are reported, not silently skipped.'),
|
|
7
|
+
outputRoots: Schema.array(Schema.string()).default([]).description('Restrict explicit output paths to these directories. Empty = unrestricted. Symlinks are resolved; the default next-to-input output is exempt.'),
|
|
8
|
+
ffmpegPath: Schema.string().description('Explicit ffmpeg binary path (overrides PATH lookup).'),
|
|
9
|
+
ffprobePath: Schema.string().description('Explicit ffprobe binary path (overrides PATH lookup).'),
|
|
10
|
+
sofficePath: Schema.string().description('Explicit LibreOffice soffice binary path (overrides auto-detection).'),
|
|
11
|
+
ghostscriptPath: Schema.string().description('Explicit Ghostscript binary path (overrides PATH lookup).'),
|
|
12
|
+
pythonPath: Schema.string().description('Explicit python interpreter for the pdf2docx-based PDF → DOCX conversion.'),
|
|
13
|
+
tesseractPath: Schema.string().description('Explicit tesseract binary for OCR (falls back to bundled tesseract.js).'),
|
|
14
|
+
maxInputMb: Schema.number().min(1).default(2048).description('Refuse inputs above this size (MB).'),
|
|
15
|
+
maxPdfPages: Schema.number().min(1).default(200).description('Full-document PDF rasterization refuses to exceed this page count; use pages for larger documents.'),
|
|
16
|
+
maxOutputPixels: Schema.number().min(100_000).default(16_000_000).description('Clamp rasterized pixels per page (width x height) to this budget.'),
|
|
17
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BinaryDependency, Logger } from '../types.js';
|
|
2
|
+
/** Persistent per-user cache for downloaded external binaries. */
|
|
3
|
+
export declare function cacheDir(): string;
|
|
4
|
+
export declare function cachedBinaryPath(name: string): string;
|
|
5
|
+
/** Expand a path pattern segment by segment; [] when nothing matches. */
|
|
6
|
+
export declare function expandPattern(pattern: string): Promise<string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolution order: explicit config override → system PATH → known install
|
|
9
|
+
* locations (Windows soffice is rarely on PATH) → plugin cache. Every
|
|
10
|
+
* candidate must pass the dependency's probe when it declares one — a config
|
|
11
|
+
* override pointing at a python without pdf2docx counts as missing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveBinaryCached(dep: BinaryDependency, overrides: Record<string, string | undefined>, which: (command: string) => Promise<string | null>, logger: Logger): Promise<string | null>;
|