@xiping/node-utils 1.0.76 → 1.0.77

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 CHANGED
@@ -6,6 +6,21 @@ Node.js 通用工具库,提供目录树、路径、SRT→VTT 字幕转换、FF
6
6
  npm install @xiping/node-utils
7
7
  ```
8
8
 
9
+ ## 命令行
10
+
11
+ 包内提供可执行命令(需本机已安装 Node;部分命令依赖 FFmpeg,见各模块说明):
12
+
13
+ | 命令 | 说明 |
14
+ |------|------|
15
+ | `video-thumbnail` | 为视频生成多帧合成缩略图(见 [src/ffmpeg/README.md](./src/ffmpeg/README.md) 中「缩略图生成」与「命令行」) |
16
+ | `translate-srt` | 字幕翻译教学相关 CLI(见 [src/subtitle-translation-teaching/README.md](./src/subtitle-translation-teaching/README.md)) |
17
+
18
+ **示例(临时执行、未安装依赖):**
19
+
20
+ ```bash
21
+ npx -p @xiping/node-utils@latest video-thumbnail --input /path/to/video.mp4
22
+ ```
23
+
9
24
  ## 使用案例
10
25
 
11
26
  ```typescript
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../lib/src/ffmpeg/cli-thumbnail.js";
@@ -140,6 +140,27 @@ console.log(result.outputPath);
140
140
  console.log(result.metadata);
141
141
  ```
142
142
 
143
+ ### 命令行(`video-thumbnail`)
144
+
145
+ 安装 `@xiping/node-utils` 后可用 `video-thumbnail`,与上方 API 选项对应(见 `--help`)。
146
+
147
+ **未在项目中安装依赖、临时在一台机器上执行:**
148
+
149
+ ```bash
150
+ npx -p @xiping/node-utils@latest video-thumbnail --input /path/to/video.mp4
151
+ ```
152
+
153
+ (包名是 `@xiping/node-utils`,命令名是 `video-thumbnail`,临时拉包时需用 `-p` 指定包名。)
154
+
155
+ **已全局安装**(之后可省略 `npx` 与 `-p`):
156
+
157
+ ```bash
158
+ npm i -g @xiping/node-utils
159
+ video-thumbnail --input /path/to/video.mp4
160
+ ```
161
+
162
+ **当前项目已依赖本包** 时,可直接 `npx video-thumbnail --input ...`,无需 `-p`。
163
+
143
164
  ### 进度回调
144
165
 
145
166
  ```typescript
@@ -0,0 +1 @@
1
+ export declare function runCli(args: string[]): Promise<void>;
@@ -0,0 +1,130 @@
1
+ import path from "node:path";
2
+ import { getThumbnail } from "./getThumbnail.js";
3
+ function printHelp() {
4
+ console.log(`Usage: video-thumbnail --input <video> [options]
5
+
6
+ Options:
7
+ -i, --input <path> Video file path (required)
8
+ --frames <n> Frame count for composition (default: 60)
9
+ --output-width <n> Output width in pixels (default: 3840)
10
+ --columns <n> Grid columns (default: 4)
11
+ --output-name <name> Output filename, e.g. thumbnail.avif (default: thumbnail.avif)
12
+ --quality <1-100> Output quality (default: 80)
13
+ --format <fmt> avif | webp | jpeg | png (default: avif)
14
+ --batch-size <n> Batch size (default: 10)
15
+ --max-concurrency <n> Max concurrency (default: 4)
16
+ --temp-dir <path> Custom temp directory
17
+ -h, --help Show this help
18
+ `);
19
+ }
20
+ function parseArgs(args) {
21
+ const options = { input: "" };
22
+ for (let i = 0; i < args.length; i++) {
23
+ const arg = args[i];
24
+ if (arg === "--input" || arg === "-i") {
25
+ options.input = args[++i] ?? "";
26
+ }
27
+ else if (arg === "--frames") {
28
+ options.frames = parseInt(args[++i] ?? "", 10);
29
+ }
30
+ else if (arg === "--output-width") {
31
+ options.outputWidth = parseInt(args[++i] ?? "", 10);
32
+ }
33
+ else if (arg === "--columns") {
34
+ options.columns = parseInt(args[++i] ?? "", 10);
35
+ }
36
+ else if (arg === "--output-name") {
37
+ options.outputFileName = args[++i] ?? "";
38
+ }
39
+ else if (arg === "--quality") {
40
+ options.quality = parseInt(args[++i] ?? "", 10);
41
+ }
42
+ else if (arg === "--format") {
43
+ const f = args[++i] ?? "";
44
+ if (f === "avif" || f === "webp" || f === "jpeg" || f === "png") {
45
+ options.format = f;
46
+ }
47
+ else {
48
+ console.error(`Error: --format must be avif, webp, jpeg, or png, got: ${f}`);
49
+ process.exit(1);
50
+ }
51
+ }
52
+ else if (arg === "--batch-size") {
53
+ options.batchSize = parseInt(args[++i] ?? "", 10);
54
+ }
55
+ else if (arg === "--max-concurrency") {
56
+ options.maxConcurrency = parseInt(args[++i] ?? "", 10);
57
+ }
58
+ else if (arg === "--temp-dir") {
59
+ options.tempDir = args[++i] ?? "";
60
+ }
61
+ else if (arg === "--help" || arg === "-h") {
62
+ options.help = true;
63
+ }
64
+ }
65
+ return options;
66
+ }
67
+ function buildThumbnailOptions(opts) {
68
+ const out = {
69
+ onProgress: (p) => {
70
+ const line = `[${p.phase}] ${p.percent}%${p.message ? ` ${p.message}` : ""}`;
71
+ console.error(line);
72
+ },
73
+ };
74
+ if (opts.frames !== undefined && !Number.isNaN(opts.frames)) {
75
+ out.frames = opts.frames;
76
+ }
77
+ if (opts.outputWidth !== undefined && !Number.isNaN(opts.outputWidth)) {
78
+ out.outputWidth = opts.outputWidth;
79
+ }
80
+ if (opts.columns !== undefined && !Number.isNaN(opts.columns)) {
81
+ out.columns = opts.columns;
82
+ }
83
+ if (opts.outputFileName) {
84
+ out.outputFileName = opts.outputFileName;
85
+ }
86
+ if (opts.quality !== undefined && !Number.isNaN(opts.quality)) {
87
+ out.quality = opts.quality;
88
+ }
89
+ if (opts.format) {
90
+ out.format = opts.format;
91
+ }
92
+ if (opts.batchSize !== undefined && !Number.isNaN(opts.batchSize)) {
93
+ out.batchSize = opts.batchSize;
94
+ }
95
+ if (opts.maxConcurrency !== undefined && !Number.isNaN(opts.maxConcurrency)) {
96
+ out.maxConcurrency = opts.maxConcurrency;
97
+ }
98
+ if (opts.tempDir) {
99
+ out.tempDir = path.resolve(opts.tempDir);
100
+ }
101
+ return out;
102
+ }
103
+ export async function runCli(args) {
104
+ const opts = parseArgs(args);
105
+ if (opts.help) {
106
+ printHelp();
107
+ return;
108
+ }
109
+ if (!opts.input) {
110
+ console.error("Error: --input is required");
111
+ printHelp();
112
+ process.exit(1);
113
+ }
114
+ const videoPath = path.resolve(process.cwd(), opts.input);
115
+ const thumbnailOpts = buildThumbnailOptions(opts);
116
+ try {
117
+ const result = await getThumbnail(videoPath, thumbnailOpts);
118
+ console.log(result.outputPath);
119
+ }
120
+ catch (e) {
121
+ const msg = e instanceof Error ? e.message : String(e);
122
+ console.error(`Error: ${msg}`);
123
+ process.exit(1);
124
+ }
125
+ }
126
+ const argv = process.argv.slice(2);
127
+ runCli(argv).catch((e) => {
128
+ console.error(e instanceof Error ? e.message : String(e));
129
+ process.exit(1);
130
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiping/node-utils",
3
- "version": "1.0.76",
3
+ "version": "1.0.77",
4
4
  "description": "node-utils",
5
5
  "type": "module",
6
6
  "author": "The-End-Hero <527409987@qq.com>",
@@ -8,14 +8,16 @@
8
8
  "license": "MIT",
9
9
  "main": "lib/index.js",
10
10
  "bin": {
11
- "translate-srt": "./bin/translate-srt"
11
+ "translate-srt": "./bin/translate-srt",
12
+ "video-thumbnail": "./bin/video-thumbnail"
12
13
  },
13
14
  "directories": {
14
15
  "lib": "lib",
15
16
  "test": "__tests__"
16
17
  },
17
18
  "files": [
18
- "lib"
19
+ "lib",
20
+ "bin"
19
21
  ],
20
22
  "repository": {
21
23
  "type": "git",
@@ -28,7 +30,7 @@
28
30
  "bugs": {
29
31
  "url": "https://github.com/The-End-Hero/xiping/issues"
30
32
  },
31
- "gitHead": "24c2d65c542f30ed4b09d7c9473a004f5e08dd3c",
33
+ "gitHead": "53cce3c7a028d237fa19f768d9a392bea054d49a",
32
34
  "publishConfig": {
33
35
  "access": "public",
34
36
  "registry": "https://registry.npmjs.org/"