@xiping/node-utils 1.0.68 → 1.0.69
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,7 +94,12 @@ function buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPat
|
|
|
91
94
|
}
|
|
92
95
|
}
|
|
93
96
|
else {
|
|
94
|
-
|
|
97
|
+
const isMacOS = os.platform() === "darwin";
|
|
98
|
+
if (!forceSoftware && isMacOS && encoders.hevcVideoToolbox) {
|
|
99
|
+
// macOS 上优先使用 VideoToolbox 硬件编码
|
|
100
|
+
args.push("-c:v", "hevc_videotoolbox");
|
|
101
|
+
}
|
|
102
|
+
else if (!forceSoftware && encoders.hevcNvenc) {
|
|
95
103
|
args.push("-c:v", "hevc_nvenc", "-preset", "slow", "-rc", "constqp", "-qp", "28");
|
|
96
104
|
}
|
|
97
105
|
else if (encoders.libx265) {
|
|
@@ -109,10 +117,10 @@ function buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPat
|
|
|
109
117
|
}
|
|
110
118
|
const PROGRESS_POLL_INTERVAL_MS = 400;
|
|
111
119
|
/** 使用 spawn 执行 ffmpeg 转码。进度通过 -progress 文件轮询获取,避免 stderr 管道缓冲导致 0 直接跳到 99。 */
|
|
112
|
-
function runTranscodeWithProgress(inputPath, outputPath, format, encoders, totalDuration, onProgress) {
|
|
120
|
+
function runTranscodeWithProgress(inputPath, outputPath, format, encoders, forceSoftware, totalDuration, onProgress) {
|
|
113
121
|
return new Promise((resolve, reject) => {
|
|
114
122
|
const progressPath = path.join(os.tmpdir(), `ffmpeg-progress-${process.pid}-${Date.now()}`);
|
|
115
|
-
const args = buildTranscodeArgs(inputPath, outputPath, format, encoders, progressPath);
|
|
123
|
+
const args = buildTranscodeArgs(inputPath, outputPath, format, encoders, forceSoftware, progressPath);
|
|
116
124
|
log("ffmpeg spawn", { totalDuration, format, outputPath, progressPath });
|
|
117
125
|
const child = spawn("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
118
126
|
let lastPercent = -1;
|
|
@@ -233,7 +241,7 @@ export async function transcoding(inputPath, config = {}) {
|
|
|
233
241
|
const reportProgress = (progress) => {
|
|
234
242
|
finalConfig.onProgress?.(progress);
|
|
235
243
|
};
|
|
236
|
-
await runTranscodeWithProgress(validatedPath, tempOutputPath, format, encoders, duration, reportProgress);
|
|
244
|
+
await runTranscodeWithProgress(validatedPath, tempOutputPath, format, encoders, finalConfig.forceSoftware, duration, reportProgress);
|
|
237
245
|
if (finalConfig.generateThumbnail) {
|
|
238
246
|
log("generating thumbnail", finalConfig.thumbnailOptions ?? {});
|
|
239
247
|
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.69",
|
|
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": "3bf85a7229343f0c7141d60ceac026773a2c98cb",
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public",
|
|
31
31
|
"registry": "https://registry.npmjs.org/"
|