@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 +15 -0
- package/bin/video-thumbnail +2 -0
- package/lib/src/ffmpeg/README.md +21 -0
- package/lib/src/ffmpeg/cli-thumbnail.d.ts +1 -0
- package/lib/src/ffmpeg/cli-thumbnail.js +130 -0
- package/package.json +6 -4
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
|
package/lib/src/ffmpeg/README.md
CHANGED
|
@@ -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.
|
|
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": "
|
|
33
|
+
"gitHead": "53cce3c7a028d237fa19f768d9a392bea054d49a",
|
|
32
34
|
"publishConfig": {
|
|
33
35
|
"access": "public",
|
|
34
36
|
"registry": "https://registry.npmjs.org/"
|