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.
Files changed (67) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +225 -0
  3. package/README.zh-CN.md +203 -0
  4. package/cordis.patch.yml +3 -0
  5. package/lib/config.d.ts +36 -0
  6. package/lib/config.js +17 -0
  7. package/lib/core/binaries/cache.d.ts +13 -0
  8. package/lib/core/binaries/cache.js +137 -0
  9. package/lib/core/binaries/download.d.ts +21 -0
  10. package/lib/core/binaries/download.js +140 -0
  11. package/lib/core/binary.d.ts +7 -0
  12. package/lib/core/binary.js +29 -0
  13. package/lib/core/converters/data.d.ts +14 -0
  14. package/lib/core/converters/data.js +145 -0
  15. package/lib/core/converters/image.d.ts +12 -0
  16. package/lib/core/converters/image.js +64 -0
  17. package/lib/core/converters/media.d.ts +21 -0
  18. package/lib/core/converters/media.js +135 -0
  19. package/lib/core/converters/office.d.ts +33 -0
  20. package/lib/core/converters/office.js +206 -0
  21. package/lib/core/converters/pdf-env.d.ts +1 -0
  22. package/lib/core/converters/pdf-env.js +8 -0
  23. package/lib/core/converters/pdf.d.ts +22 -0
  24. package/lib/core/converters/pdf.js +316 -0
  25. package/lib/core/detect.d.ts +16 -0
  26. package/lib/core/detect.js +121 -0
  27. package/lib/core/errors.d.ts +5 -0
  28. package/lib/core/errors.js +16 -0
  29. package/lib/core/formats.d.ts +11 -0
  30. package/lib/core/formats.js +60 -0
  31. package/lib/core/index.d.ts +25 -0
  32. package/lib/core/index.js +52 -0
  33. package/lib/core/inspect.d.ts +13 -0
  34. package/lib/core/inspect.js +134 -0
  35. package/lib/core/ocr.d.ts +38 -0
  36. package/lib/core/ocr.js +152 -0
  37. package/lib/core/optimizers.d.ts +29 -0
  38. package/lib/core/optimizers.js +275 -0
  39. package/lib/core/paths.d.ts +11 -0
  40. package/lib/core/paths.js +19 -0
  41. package/lib/core/router.d.ts +64 -0
  42. package/lib/core/router.js +288 -0
  43. package/lib/core/types.d.ts +199 -0
  44. package/lib/core/types.js +8 -0
  45. package/lib/core/utils/exec.d.ts +38 -0
  46. package/lib/core/utils/exec.js +89 -0
  47. package/lib/core/utils/pages.d.ts +12 -0
  48. package/lib/core/utils/pages.js +46 -0
  49. package/lib/format.d.ts +23 -0
  50. package/lib/format.js +84 -0
  51. package/lib/index.d.ts +6 -0
  52. package/lib/index.js +51 -0
  53. package/lib/tools/batch-convert.d.ts +5 -0
  54. package/lib/tools/batch-convert.js +155 -0
  55. package/lib/tools/convert-file.d.ts +3 -0
  56. package/lib/tools/convert-file.js +57 -0
  57. package/lib/tools/inspect-file.d.ts +3 -0
  58. package/lib/tools/inspect-file.js +29 -0
  59. package/lib/tools/install-media.d.ts +9 -0
  60. package/lib/tools/install-media.js +55 -0
  61. package/lib/tools/install-ocr.d.ts +7 -0
  62. package/lib/tools/install-ocr.js +43 -0
  63. package/lib/tools/list-conversions.d.ts +2 -0
  64. package/lib/tools/list-conversions.js +18 -0
  65. package/lib/tools/optimize-file.d.ts +3 -0
  66. package/lib/tools/optimize-file.js +91 -0
  67. package/package.json +66 -0
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { defineTool } from '@deepseek-ai/dsh-tools';
4
+ import { DetectError, optimizeFile, resolveBinary } from '../core/index.js';
5
+ import { canonicalExtension } from '../core/index.js';
6
+ import { formatBytes, formatDuration, formatFailure } from '../format.js';
7
+ export function createOptimizeFileTool(router, config, logger) {
8
+ return defineTool({
9
+ name: 'optimize_file',
10
+ description: 'Shrink a file toward a target size, fully local: MP4/MOV video via two-pass x264 (bitrate computed from the target, output is MP4), JPG/WEBP/PNG images via encoder quality search, and PDF via Ghostscript presets (printer/ebook/screen). Not for GIF yet. Video needs ffmpeg+ffprobe and PDF needs Ghostscript installed; images work without any external tool.',
11
+ parameters: {
12
+ input: { type: 'string', required: true, description: 'Absolute path of the file to shrink.' },
13
+ target_size_mb: { type: 'number', required: true, description: 'Desired maximum output size in megabytes.' },
14
+ output: {
15
+ type: 'string',
16
+ description: 'Optional absolute output path. Defaults to next to the input with a "-min" suffix. If the plugin config sets outputRoots, the path must be inside one of them.',
17
+ },
18
+ overwrite: { type: 'boolean', description: 'Replace the output file if it exists. Default false.' },
19
+ },
20
+ output: {
21
+ schema: { type: 'string' },
22
+ render: (_args, value) => [{ type: 'text', text: value }],
23
+ },
24
+ timeoutMs: Math.max(config.timeoutMs, 600_000), // two-pass video needs room
25
+ isConcurrencySafe: () => true,
26
+ async execute(args, exec) {
27
+ const targetBytes = Math.max(1, Math.round(args.target_size_mb * 1024 * 1024));
28
+ let detection;
29
+ try {
30
+ detection = (await router.detect(args.input)).detection;
31
+ }
32
+ catch (err) {
33
+ if (err instanceof DetectError)
34
+ throw new Error(formatFailure(err.error));
35
+ throw err;
36
+ }
37
+ const format = detection.format;
38
+ // Default output: next to the input, same base + "-min", proper extension.
39
+ const outExt = format === 'mov' ? '.mp4' : canonicalExtension(format);
40
+ const output = args.output ??
41
+ path.join(path.dirname(args.input), `${path.basename(args.input, path.extname(args.input))}-min${outExt}`);
42
+ if (path.resolve(output) === path.resolve(args.input)) {
43
+ throw new Error('Output path equals the input path; optimizing would destroy the source. Use the default -min output name or pick another path.');
44
+ }
45
+ if (args.output !== undefined && config.outputRoots.length > 0 && !isInsideRoots(args.output, config.outputRoots)) {
46
+ throw new Error(`Output path is outside every configured outputRoot (${config.outputRoots.join(', ')}).`);
47
+ }
48
+ if (args.overwrite !== true && (await exists(output))) {
49
+ throw new Error(`Output file already exists: ${output}. Pass overwrite: true to replace it.`);
50
+ }
51
+ const overrides = {};
52
+ if (config.ffmpegPath)
53
+ overrides.ffmpegPath = config.ffmpegPath;
54
+ if (config.ffprobePath)
55
+ overrides.ffprobePath = config.ffprobePath;
56
+ const resolve = (dep) => resolveBinary(dep, overrides, logger);
57
+ const result = await optimizeFile(args.input, targetBytes, output, format, resolve, { logger, signal: exec.signal, timeoutMs: Math.max(config.timeoutMs, 600_000) });
58
+ if (!result.ok)
59
+ throw new Error(formatFailure(result.error));
60
+ const lines = [
61
+ `Optimized: ${result.input} (${format}) -> ${result.output}`,
62
+ `${formatBytes(result.bytesIn)} -> ${formatBytes(result.bytesOut)} (target ${args.target_size_mb} MB) in ${formatDuration(result.durationMs)}`,
63
+ `Applied: ${result.detail}`,
64
+ ];
65
+ if (result.bytesOut > targetBytes) {
66
+ lines.push(`Warning: result is still above the target; try a higher target_size_mb.`);
67
+ }
68
+ for (const warning of result.warnings)
69
+ lines.push(`Warning: ${warning}`);
70
+ return lines.join('\n');
71
+ },
72
+ });
73
+ }
74
+ function isInsideRoots(output, roots) {
75
+ const resolved = path.resolve(output);
76
+ const candidate = process.platform === 'win32' ? resolved.replace(/\\/g, '/').toLowerCase() : resolved;
77
+ return roots.some((root) => {
78
+ const rr = path.resolve(root);
79
+ const prefix = process.platform === 'win32' ? rr.replace(/\\/g, '/').toLowerCase() : rr;
80
+ return candidate === prefix || candidate.startsWith(prefix.endsWith('/') ? prefix : prefix + '/');
81
+ });
82
+ }
83
+ async function exists(p) {
84
+ try {
85
+ await fs.access(p);
86
+ return true;
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "dsh-file-convert",
3
+ "version": "0.4.0",
4
+ "description": "Local-first file conversion for DeepSeek Harness - images, PDF, data, audio/video and office documents. No API keys, no uploads, no token cost.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./lib/index.js",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/zzy-12345678/dsh-file-convert.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/zzy-12345678/dsh-file-convert/issues"
14
+ },
15
+ "homepage": "https://github.com/zzy-12345678/dsh-file-convert#readme",
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "cordis.patch.yml",
22
+ "README.md",
23
+ "README.zh-CN.md",
24
+ "LICENSE"
25
+ ],
26
+ "dsh": {
27
+ "bundle": {
28
+ "patch": "./cordis.patch.yml"
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "prepare": "npm run build",
34
+ "test": "vitest run",
35
+ "smoke": "node scripts/smoke-test.mjs"
36
+ },
37
+ "keywords": [
38
+ "dsh-plugin",
39
+ "deepseek-harness",
40
+ "convert",
41
+ "file-conversion",
42
+ "local-first"
43
+ ],
44
+ "dependencies": {
45
+ "@deepseek-ai/cordis": "^4.0.1",
46
+ "@deepseek-ai/dsh-tools": "0.0.1-rc.1",
47
+ "@deepseek-ai/schemastery": "^3.18.1",
48
+ "@napi-rs/canvas": "^1.0.8",
49
+ "csv-parse": "^7.0.2",
50
+ "csv-stringify": "^6.8.3",
51
+ "file-type": "^22.0.2",
52
+ "js-yaml": "^4.1.0",
53
+ "pdfjs-dist": "^6.2.108",
54
+ "sharp": "^0.35.4",
55
+ "tesseract.js": "^7.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/js-yaml": "^4.0.9",
59
+ "@types/node": "^22.0.0",
60
+ "docx": "^9.7.1",
61
+ "fflate": "^0.8.3",
62
+ "pdf-lib": "^1.17.1",
63
+ "typescript": "^5.9.0",
64
+ "vitest": "^4.1.11"
65
+ }
66
+ }