@xiping/node-utils 1.0.68 → 1.0.70
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.
|
@@ -19,6 +19,8 @@ export interface TranscodingConfig {
|
|
|
19
19
|
thumbnailOptions?: Partial<ThumbnailOptions>;
|
|
20
20
|
/** 目标编码格式,默认 'hevc' */
|
|
21
21
|
format?: "av1" | "hevc";
|
|
22
|
+
/** 是否强制使用软编码(禁用硬件编码),默认 false */
|
|
23
|
+
forceSoftware?: boolean;
|
|
22
24
|
/** 是否替换原文件(删除原文件并将输出重命名为原路径),默认 false */
|
|
23
25
|
replaceOriginal?: boolean;
|
|
24
26
|
/** 转换进度回调 */
|
|
@@ -29,6 +31,8 @@ export interface AvailableEncoders {
|
|
|
29
31
|
libaomAv1: boolean;
|
|
30
32
|
hevcNvenc: boolean;
|
|
31
33
|
libx265: boolean;
|
|
34
|
+
/** macOS 上的 HEVC 硬编码(VideoToolbox) */
|
|
35
|
+
hevcVideoToolbox: boolean;
|
|
32
36
|
}
|
|
33
37
|
/** 同步检测可用编码器 */
|
|
34
38
|
export declare function getAvailableEncoders(): AvailableEncoders;
|
|
@@ -11,6 +11,7 @@ const log = (message, data) => {
|
|
|
11
11
|
const defaultConfig = {
|
|
12
12
|
generateThumbnail: true,
|
|
13
13
|
format: "hevc",
|
|
14
|
+
forceSoftware: false,
|
|
14
15
|
replaceOriginal: false,
|
|
15
16
|
};
|
|
16
17
|
/** 同步检测可用编码器 */
|
|
@@ -22,6 +23,8 @@ export function getAvailableEncoders() {
|
|
|
22
23
|
libaomAv1: stdout.includes("libaom-av1"),
|
|
23
24
|
hevcNvenc: stdout.includes("hevc_nvenc"),
|
|
24
25
|
libx265: stdout.includes("libx265"),
|
|
26
|
+
// macOS VideoToolbox 硬件编码(HEVC)
|
|
27
|
+
hevcVideoToolbox: stdout.includes("hevc_videotoolbox"),
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
function validateAndSanitizePath(inputPath) {
|
|
@@ -77,10 +80,10 @@ function parseOutTimeFromProgressFile(content) {
|
|
|
77
80
|
return lastUs != null ? lastUs / 1e6 : null;
|
|
78
81
|
}
|
|
79
82
|
/** 根据格式与可用编码器构建 ffmpeg 参数数组;progressPath 用于 -progress 以获取实时进度(避免 stderr 缓冲导致 0 直接跳到 99) */
|
|
80
|
-
function buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPath) {
|
|
83
|
+
function buildTranscodeArgs(inputPath, outputPath, format, encoders, forceSoftware, progressPath) {
|
|
81
84
|
const args = ["-i", inputPath];
|
|
82
85
|
if (format === "av1") {
|
|
83
|
-
if (encoders.av1Nvenc) {
|
|
86
|
+
if (!forceSoftware && encoders.av1Nvenc) {
|
|
84
87
|
args.push("-c:v", "av1_nvenc", "-preset", "slow", "-rc", "constqp", "-qp", "28");
|
|
85
88
|
}
|
|
86
89
|
else if (encoders.libaomAv1) {
|
|
@@ -91,11 +94,17 @@ function buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPat
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
else {
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
const isMacOS = os.platform() === "darwin";
|
|
98
|
+
if (!forceSoftware && isMacOS && encoders.hevcVideoToolbox) {
|
|
99
|
+
// macOS 上优先使用 VideoToolbox 硬件编码
|
|
100
|
+
// 使用固定质量参数,避免完全依赖默认码率/质量导致输出不可控
|
|
101
|
+
args.push("-c:v", "hevc_videotoolbox", "-q:v", "60");
|
|
102
|
+
}
|
|
103
|
+
else if (!forceSoftware && encoders.hevcNvenc) {
|
|
104
|
+
args.push("-c:v", "hevc_nvenc", "-preset", "slow", "-rc", "constqp", "-qp", "22");
|
|
96
105
|
}
|
|
97
106
|
else if (encoders.libx265) {
|
|
98
|
-
args.push("-c:v", "libx265", "-crf", "
|
|
107
|
+
args.push("-c:v", "libx265", "-crf", "22");
|
|
99
108
|
}
|
|
100
109
|
else {
|
|
101
110
|
throw new Error("没有可用的 HEVC 编码器");
|
|
@@ -109,10 +118,10 @@ function buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPat
|
|
|
109
118
|
}
|
|
110
119
|
const PROGRESS_POLL_INTERVAL_MS = 400;
|
|
111
120
|
/** 使用 spawn 执行 ffmpeg 转码。进度通过 -progress 文件轮询获取,避免 stderr 管道缓冲导致 0 直接跳到 99。 */
|
|
112
|
-
function runTranscodeWithProgress(inputPath, outputPath, format, encoders, totalDuration, onProgress) {
|
|
121
|
+
function runTranscodeWithProgress(inputPath, outputPath, format, encoders, forceSoftware, totalDuration, onProgress) {
|
|
113
122
|
return new Promise((resolve, reject) => {
|
|
114
123
|
const progressPath = path.join(os.tmpdir(), `ffmpeg-progress-${process.pid}-${Date.now()}`);
|
|
115
|
-
const args = buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPath);
|
|
124
|
+
const args = buildTranscodeArgs(inputPath, outputPath, format, encoders, forceSoftware, progressPath);
|
|
116
125
|
log("ffmpeg spawn", { totalDuration, format, outputPath, progressPath });
|
|
117
126
|
const child = spawn("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
118
127
|
let lastPercent = -1;
|
|
@@ -233,7 +242,7 @@ export async function transcoding(inputPath, config = {}) {
|
|
|
233
242
|
const reportProgress = (progress) => {
|
|
234
243
|
finalConfig.onProgress?.(progress);
|
|
235
244
|
};
|
|
236
|
-
await runTranscodeWithProgress(validatedPath, tempOutputPath, format, encoders, duration, reportProgress);
|
|
245
|
+
await runTranscodeWithProgress(validatedPath, tempOutputPath, format, encoders, finalConfig.forceSoftware, duration, reportProgress);
|
|
237
246
|
if (finalConfig.generateThumbnail) {
|
|
238
247
|
log("generating thumbnail", finalConfig.thumbnailOptions ?? {});
|
|
239
248
|
await getThumbnail(validatedPath, finalConfig.thumbnailOptions ?? {});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiping/node-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.70",
|
|
4
4
|
"description": "node-utils",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "The-End-Hero <527409987@qq.com>",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"bugs": {
|
|
26
26
|
"url": "https://github.com/The-End-Hero/xiping/issues"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "61f56a865103626696a6b73c0527c2cb5fc859c1",
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public",
|
|
31
31
|
"registry": "https://registry.npmjs.org/"
|