dev-toolkit-cli 1.1.1 → 1.1.3
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/bin/actions/action-compress.js +9 -8
- package/bin/actions/action-compress.js.map +1 -1
- package/bin/lib/bash.js +126 -26
- package/bin/lib/bash.js.map +1 -1
- package/bin/lib/constants.js +8 -32
- package/bin/lib/constants.js.map +1 -1
- package/package.json +1 -1
- package/src/actions/action-compress.ts +10 -8
- package/src/lib/bash.ts +151 -30
- package/src/lib/constants.ts +12 -35
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import mime from "mime";
|
|
3
|
-
import { getFolderFiles, replaceFilenameExtension, } from "../lib/utils/index.js";
|
|
4
|
-
import { check_dependencies, ffmpeg_image_to_webp,
|
|
3
|
+
import { addFilenameSuffix, getFolderFiles, replaceFilenameExtension, } from "../lib/utils/index.js";
|
|
4
|
+
import { check_dependencies, ffmpeg_image_to_webp, ffmpeg_compress_mp4_gpu, } from "../lib/bash.js";
|
|
5
5
|
export const FileType = ["image", "video"];
|
|
6
6
|
const unsupportedFormats = {
|
|
7
7
|
image: ["image/webp", "image/gif", "image/svg+xml"],
|
|
@@ -13,7 +13,7 @@ export async function file(pathname, { verbose, type, resolution, force, horizon
|
|
|
13
13
|
console.warn("⚠️ Ignoring already compressed files");
|
|
14
14
|
if (horizontal)
|
|
15
15
|
console.warn("⚠️ Forcing horizontal aspect ratio 16x9");
|
|
16
|
-
let verbose_title = "";
|
|
16
|
+
// let verbose_title = "";
|
|
17
17
|
const filePathList = await getFolderFiles(pathname, true);
|
|
18
18
|
for (let filePath of filePathList) {
|
|
19
19
|
const mimetype = mime.getType(filePath);
|
|
@@ -21,16 +21,17 @@ export async function file(pathname, { verbose, type, resolution, force, horizon
|
|
|
21
21
|
continue;
|
|
22
22
|
const fileType = mimetype.split("/")[0];
|
|
23
23
|
if (!(type ? [type] : FileType).includes(fileType) ||
|
|
24
|
-
(unsupportedFormats[fileType] || []).includes(mimetype)
|
|
24
|
+
(unsupportedFormats[fileType] || []).includes(mimetype) ||
|
|
25
|
+
filePath.includes(".compressed"))
|
|
25
26
|
continue;
|
|
26
27
|
const title = filePath
|
|
27
28
|
.substring(pathname.length)
|
|
28
29
|
.split("/")
|
|
29
30
|
.slice(1, 1 + verbose)
|
|
30
31
|
.join("/");
|
|
31
|
-
if (title !== verbose_title)
|
|
32
|
-
|
|
33
|
-
verbose_title = title;
|
|
32
|
+
// if (title !== verbose_title)
|
|
33
|
+
// console.log(`Compressing ${fileType} at:`, title);
|
|
34
|
+
// verbose_title = title;
|
|
34
35
|
try {
|
|
35
36
|
if (fileType === "image")
|
|
36
37
|
await compressImage(filePath);
|
|
@@ -51,7 +52,7 @@ async function compressImage(filePath) {
|
|
|
51
52
|
await fs.rm(filePath);
|
|
52
53
|
}
|
|
53
54
|
async function compressVideo(filePath, resolution, force) {
|
|
54
|
-
await
|
|
55
|
+
await ffmpeg_compress_mp4_gpu(filePath, addFilenameSuffix(filePath, ".compressed"), resolution, force);
|
|
55
56
|
await fs.rm(filePath);
|
|
56
57
|
}
|
|
57
58
|
//# sourceMappingURL=action-compress.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-compress.js","sourceRoot":"","sources":["../../src/actions/action-compress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAIxB,OAAO,
|
|
1
|
+
{"version":3,"file":"action-compress.js","sourceRoot":"","sources":["../../src/actions/action-compress.ts"],"names":[],"mappings":"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"}
|
package/bin/lib/bash.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { exec } from "child_process";
|
|
2
|
+
import { resolutionMapping } from "./constants.js";
|
|
2
3
|
import { CommanderError } from "commander";
|
|
3
4
|
import * as os from "os";
|
|
4
5
|
export const execAsync = (command) => new Promise((resolve, reject) => {
|
|
@@ -27,9 +28,6 @@ export async function check_dependencies(dependencies = globalDependencies) {
|
|
|
27
28
|
}
|
|
28
29
|
// Cache para não termos que detectar a GPU toda vez que formos comprimir um vídeo
|
|
29
30
|
let cachedGpuType = null;
|
|
30
|
-
/**
|
|
31
|
-
* Detecta a GPU instalada na máquina rodando comandos nativos do SO.
|
|
32
|
-
*/
|
|
33
31
|
async function detectGPU() {
|
|
34
32
|
if (cachedGpuType)
|
|
35
33
|
return cachedGpuType;
|
|
@@ -60,47 +58,149 @@ async function detectGPU() {
|
|
|
60
58
|
}
|
|
61
59
|
return cachedGpuType;
|
|
62
60
|
}
|
|
63
|
-
export async function
|
|
64
|
-
const resolutionMapping = {
|
|
65
|
-
"4320p": { w: 7680, h: 4320 }, // 8K
|
|
66
|
-
"2160p": { w: 3840, h: 2160 }, // 4K
|
|
67
|
-
"1440p": { w: 2560, h: 1440 }, // Quad HD
|
|
68
|
-
"1080p": { w: 1920, h: 1080 }, // Full HD
|
|
69
|
-
"720p": { w: 1280, h: 720 }, // HD
|
|
70
|
-
"480p": { w: 854, h: 480 }, // SD (WVGA)
|
|
71
|
-
"360p": { w: 640, h: 360 }, // nHD
|
|
72
|
-
"240p": { w: 426, h: 240 }, // WQVGA
|
|
73
|
-
};
|
|
74
|
-
const { w, h } = resolutionMapping[resolution];
|
|
61
|
+
export async function ffmpeg_video_to_webm(input, output) {
|
|
75
62
|
const gpu = await detectGPU();
|
|
76
63
|
const isLinux = os.platform() === "linux";
|
|
77
|
-
//
|
|
64
|
+
// 1. Mapeia os Encoders VP9 para usar as GPUs
|
|
78
65
|
const encoders = {
|
|
79
66
|
nvidia: "-c:v vp9_nvenc -preset p4",
|
|
80
67
|
amd: isLinux ? "-c:v vp9_vaapi" : "-c:v vp9_amf",
|
|
81
68
|
intel: isLinux ? "-c:v vp9_vaapi" : "-c:v vp9_qsv",
|
|
82
|
-
mac: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
69
|
+
mac: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4", // Fallback (Mac não expõe VP9 nativo no FFmpeg)
|
|
83
70
|
cpu: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
84
71
|
};
|
|
85
72
|
const vcodec = encoders[gpu] || encoders.cpu;
|
|
86
|
-
let hwaccel =
|
|
73
|
+
let hwaccel = "";
|
|
74
|
+
let videoFilter = "";
|
|
75
|
+
// 2. Lógica VA-API (Linux) para manter todo o fluxo de frames na GPU
|
|
76
|
+
if (isLinux && (gpu === "amd" || gpu === "intel")) {
|
|
77
|
+
// Estas flags transferem a decodificação para a GPU e mantêm os frames na memória de vídeo
|
|
78
|
+
hwaccel =
|
|
79
|
+
"-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi";
|
|
80
|
+
// Como não vamos mudar a resolução, o filtro apenas formata e sobe para a GPU caso o input não venha em formato compatível
|
|
81
|
+
videoFilter = `-vf "format=nv12,hwupload"`;
|
|
82
|
+
}
|
|
83
|
+
else if (gpu === "nvidia" || gpu === "amd" || gpu === "intel") {
|
|
84
|
+
hwaccel = "-hwaccel auto";
|
|
85
|
+
}
|
|
86
|
+
// 3. Áudio para WebM
|
|
87
|
+
const acodec = "-c:a libopus -b:a 128k";
|
|
88
|
+
// 4. Monta o comando (removido o -s e -filter:v de redimensionamento)
|
|
89
|
+
const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter ? videoFilter : ""} ${vcodec} ${acodec} -f webm -y "${output}"`;
|
|
90
|
+
console.log(`Compressing video [${gpu}]:`, input);
|
|
91
|
+
return execAsync(command);
|
|
92
|
+
}
|
|
93
|
+
// /**
|
|
94
|
+
// * Detecta a GPU instalada na máquina rodando comandos nativos do SO.
|
|
95
|
+
// */
|
|
96
|
+
// async function detectGPU(): Promise<GpuType> {
|
|
97
|
+
// if (cachedGpuType) return cachedGpuType;
|
|
98
|
+
// const platform = os.platform();
|
|
99
|
+
// try {
|
|
100
|
+
// if (platform === "darwin") return (cachedGpuType = "mac");
|
|
101
|
+
// let output = "";
|
|
102
|
+
// if (platform === "win32") {
|
|
103
|
+
// const stdout = await execAsync(
|
|
104
|
+
// "wmic path win32_VideoController get name",
|
|
105
|
+
// );
|
|
106
|
+
// output = stdout.toLowerCase();
|
|
107
|
+
// } else if (platform === "linux") {
|
|
108
|
+
// const stdout = await execAsync("lspci");
|
|
109
|
+
// output = stdout.toLowerCase();
|
|
110
|
+
// }
|
|
111
|
+
// if (output.includes("nvidia")) cachedGpuType = "nvidia";
|
|
112
|
+
// else if (output.includes("amd") || output.includes("radeon"))
|
|
113
|
+
// cachedGpuType = "amd";
|
|
114
|
+
// else if (output.includes("intel")) cachedGpuType = "intel";
|
|
115
|
+
// else cachedGpuType = "cpu";
|
|
116
|
+
// } catch (error) {
|
|
117
|
+
// cachedGpuType = "cpu";
|
|
118
|
+
// }
|
|
119
|
+
// return cachedGpuType;
|
|
120
|
+
// }
|
|
121
|
+
// export async function ffmpeg_video_compress_webm(
|
|
122
|
+
// input: string,
|
|
123
|
+
// output: string,
|
|
124
|
+
// resolution: Resolution = "480p",
|
|
125
|
+
// forceResolution?: boolean,
|
|
126
|
+
// ) {
|
|
127
|
+
// const { w, h } = resolutionMapping[resolution];
|
|
128
|
+
// const gpu = await detectGPU();
|
|
129
|
+
// const isLinux = os.platform() === "linux";
|
|
130
|
+
// // 1. Mapeia os Encoders para o codec VP9
|
|
131
|
+
// const encoders = {
|
|
132
|
+
// nvidia: "-c:v vp9_nvenc -preset p4",
|
|
133
|
+
// // Removemos o vp9_vaapi da AMD no Linux, caindo para a CPU (libvpx-vp9)
|
|
134
|
+
// amd: isLinux ? "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4" : "-c:v vp9_amf",
|
|
135
|
+
// intel: isLinux ? "-c:v vp9_vaapi" : "-c:v vp9_qsv", // Intel costuma suportar melhor vp9_vaapi, mas pode dar o mesmo erro dependendo da geração
|
|
136
|
+
// mac: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
137
|
+
// cpu: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
138
|
+
// };
|
|
139
|
+
// const vcodec = encoders[gpu] || encoders.cpu;
|
|
140
|
+
// // 2. Só ativamos o hwaccel se NÃO formos usar a CPU (libvpx-vp9)
|
|
141
|
+
// // Como a AMD no Linux agora usa libvpx-vp9, ela entra na regra de desativar o hwaccel para encode
|
|
142
|
+
// const isUsingCpuEncoder = vcodec.includes("libvpx");
|
|
143
|
+
// let hwaccel = isUsingCpuEncoder ? "" : "-hwaccel auto";
|
|
144
|
+
// let videoFilter = "";
|
|
145
|
+
// // 3. Lógica específica VA-API (agora só ativada se a Intel for usar hardware)
|
|
146
|
+
// if (isLinux && !isUsingCpuEncoder && gpu === "intel") {
|
|
147
|
+
// hwaccel = "-vaapi_device /dev/dri/renderD128";
|
|
148
|
+
// videoFilter = forceResolution
|
|
149
|
+
// ? `-vf "scale=${w}:${h},format=nv12,hwupload"`
|
|
150
|
+
// : `-vf "scale=-1:${h},format=nv12,hwupload"`;
|
|
151
|
+
// } else {
|
|
152
|
+
// // Escala normal via CPU (necessária para o libvpx-vp9)
|
|
153
|
+
// videoFilter = forceResolution ? `-s ${w}x${h}` : `-filter:v scale=-1:${h}`;
|
|
154
|
+
// }
|
|
155
|
+
// // O vídeo WebM necessita de codecs de áudio específicos como o Opus
|
|
156
|
+
// const acodec = "-c:a libopus -b:a 128k";
|
|
157
|
+
// // A flag '-f webm' força explicitamente a criação de um contentor de vídeo WebM
|
|
158
|
+
// const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter} ${vcodec} ${acodec} -f webm -y "${output}"`;
|
|
159
|
+
// return execAsync(command);
|
|
160
|
+
// }
|
|
161
|
+
/**
|
|
162
|
+
* Comprime vídeo para MP4 (H.264) utilizando a GPU disponível com taxa de compressão otimizada.
|
|
163
|
+
*/
|
|
164
|
+
export async function ffmpeg_compress_mp4_gpu(input, output, // O arquivo deve terminar em .mp4
|
|
165
|
+
resolution = "480p", forceResolution) {
|
|
166
|
+
const { w, h } = resolutionMapping[resolution];
|
|
167
|
+
const gpu = await detectGPU();
|
|
168
|
+
const isLinux = os.platform() === "linux";
|
|
169
|
+
// 1. Mapeia os Encoders H.264 com equivalência de compressão (Qualidade vs Tamanho)
|
|
170
|
+
// O valor 28 é o "sweet spot". Menor = mais qualidade/mais pesado. Maior = menos qualidade/mais leve.
|
|
171
|
+
const encoders = {
|
|
172
|
+
nvidia: "-c:v h264_nvenc -preset p4 -cq 28",
|
|
173
|
+
amd: isLinux ? "-c:v h264_vaapi -qp 28" : "-c:v h264_amf -qp_i 28 -qp_p 28",
|
|
174
|
+
intel: isLinux
|
|
175
|
+
? "-c:v h264_vaapi -qp 28"
|
|
176
|
+
: "-c:v h264_qsv -global_quality 28",
|
|
177
|
+
mac: "-c:v h264_videotoolbox -q:v 60", // No Mac a escala é diferente, 60 é um bom balanço
|
|
178
|
+
cpu: "-c:v libx264 -preset fast -crf 28",
|
|
179
|
+
};
|
|
180
|
+
const vcodec = encoders[gpu] || encoders.cpu;
|
|
181
|
+
let hwaccel = gpu === "cpu" ? "" : "-hwaccel auto";
|
|
87
182
|
let videoFilter = "";
|
|
183
|
+
// 2. Define a string base do redimensionamento (Scale)
|
|
184
|
+
const scaleStr = forceResolution ? `scale=${w}:${h}` : `scale=-1:${h}`;
|
|
185
|
+
// 3. Aplica o filtro correto baseado na necessidade de VA-API no Linux
|
|
88
186
|
if (isLinux && (gpu === "amd" || gpu === "intel")) {
|
|
89
187
|
hwaccel = "-vaapi_device /dev/dri/renderD128";
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
: `-vf "scale=-1:${h},format=nv12,hwupload"`;
|
|
188
|
+
// Integra a resolução e o upload para a GPU no mesmo filtro
|
|
189
|
+
videoFilter = `-vf "${scaleStr},format=nv12,hwupload"`;
|
|
93
190
|
}
|
|
94
191
|
else {
|
|
95
|
-
|
|
192
|
+
// Para NVIDIA, Mac e Windows, o scale simples via software antes de subir pra GPU funciona melhor
|
|
193
|
+
videoFilter = `-vf "${scaleStr}"`;
|
|
96
194
|
}
|
|
97
|
-
//
|
|
98
|
-
const acodec = "-c:a
|
|
99
|
-
|
|
100
|
-
|
|
195
|
+
// 4. Áudio AAC universal e forçamento do container MP4
|
|
196
|
+
const acodec = "-c:a aac -b:a 128k";
|
|
197
|
+
const format = "-f mp4";
|
|
198
|
+
// 5. Monta e executa o comando final
|
|
199
|
+
const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter} ${vcodec} ${acodec} ${format} -y "${output}"`;
|
|
101
200
|
return execAsync(command);
|
|
102
201
|
}
|
|
103
202
|
export async function ffmpeg_image_to_webp(input, output) {
|
|
203
|
+
console.log(`Compressing image:`, input);
|
|
104
204
|
return execAsync(`ffmpeg -i "${input}" -c:v libwebp "${output}"`);
|
|
105
205
|
}
|
|
106
206
|
//# sourceMappingURL=bash.js.map
|
package/bin/lib/bash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/lib/bash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/lib/bash.ts"],"names":[],"mappings":"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"}
|
package/bin/lib/constants.js
CHANGED
|
@@ -1,36 +1,12 @@
|
|
|
1
1
|
export const resolutionMapping = {
|
|
2
|
-
"4320p": {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
},
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"1440p": {
|
|
11
|
-
w: 2560,
|
|
12
|
-
h: 1440,
|
|
13
|
-
},
|
|
14
|
-
"1080p": {
|
|
15
|
-
w: 1920,
|
|
16
|
-
h: 1080,
|
|
17
|
-
},
|
|
18
|
-
"720p": {
|
|
19
|
-
w: 1280,
|
|
20
|
-
h: 720,
|
|
21
|
-
},
|
|
22
|
-
"480p": {
|
|
23
|
-
w: 854,
|
|
24
|
-
h: 480,
|
|
25
|
-
},
|
|
26
|
-
"360p": {
|
|
27
|
-
w: 640,
|
|
28
|
-
h: 360,
|
|
29
|
-
},
|
|
30
|
-
"240p": {
|
|
31
|
-
w: 426,
|
|
32
|
-
h: 240,
|
|
33
|
-
},
|
|
2
|
+
"4320p": { w: 7680, h: 4320 }, // 8K
|
|
3
|
+
"2160p": { w: 3840, h: 2160 }, // 4K
|
|
4
|
+
"1440p": { w: 2560, h: 1440 }, // Quad HD
|
|
5
|
+
"1080p": { w: 1920, h: 1080 }, // Full HD
|
|
6
|
+
"720p": { w: 1280, h: 720 }, // HD
|
|
7
|
+
"480p": { w: 854, h: 480 }, // SD (WVGA)
|
|
8
|
+
"360p": { w: 640, h: 360 }, // nHD
|
|
9
|
+
"240p": { w: 426, h: 240 }, // WQVGA
|
|
34
10
|
};
|
|
35
11
|
export const Resolution = [
|
|
36
12
|
"4320p",
|
package/bin/lib/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAE1B;IACF,OAAO,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK;IACpC,OAAO,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK;IACpC,OAAO,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,UAAU;IACzC,OAAO,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,UAAU;IACzC,MAAM,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,KAAK;IAClC,MAAM,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,YAAY;IACxC,MAAM,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM;IAClC,MAAM,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,QAAQ;CACrC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACE,CAAC"}
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import path from "path";
|
|
|
6
6
|
import { Resolution } from "../lib/constants.js";
|
|
7
7
|
import {
|
|
8
8
|
addFilenamePrefix,
|
|
9
|
+
addFilenameSuffix,
|
|
9
10
|
getAvailablePathname,
|
|
10
11
|
getDirectory,
|
|
11
12
|
getFilename,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
import {
|
|
18
19
|
check_dependencies,
|
|
19
20
|
ffmpeg_image_to_webp,
|
|
20
|
-
|
|
21
|
+
ffmpeg_compress_mp4_gpu,
|
|
21
22
|
} from "../lib/bash.js";
|
|
22
23
|
|
|
23
24
|
export const FileType = ["image", "video"] as const;
|
|
@@ -48,7 +49,7 @@ export async function file(
|
|
|
48
49
|
if (force) console.warn("⚠️ Ignoring already compressed files");
|
|
49
50
|
if (horizontal) console.warn("⚠️ Forcing horizontal aspect ratio 16x9");
|
|
50
51
|
|
|
51
|
-
let verbose_title = "";
|
|
52
|
+
// let verbose_title = "";
|
|
52
53
|
|
|
53
54
|
const filePathList = await getFolderFiles(pathname, true);
|
|
54
55
|
|
|
@@ -58,7 +59,8 @@ export async function file(
|
|
|
58
59
|
const fileType = mimetype.split("/")[0] as FileType;
|
|
59
60
|
if (
|
|
60
61
|
!(type ? [type] : FileType).includes(fileType as FileType) ||
|
|
61
|
-
(unsupportedFormats[fileType] || []).includes(mimetype)
|
|
62
|
+
(unsupportedFormats[fileType] || []).includes(mimetype) ||
|
|
63
|
+
filePath.includes(".compressed")
|
|
62
64
|
)
|
|
63
65
|
continue;
|
|
64
66
|
|
|
@@ -68,10 +70,10 @@ export async function file(
|
|
|
68
70
|
.slice(1, 1 + verbose)
|
|
69
71
|
.join("/");
|
|
70
72
|
|
|
71
|
-
if (title !== verbose_title)
|
|
72
|
-
|
|
73
|
+
// if (title !== verbose_title)
|
|
74
|
+
// console.log(`Compressing ${fileType} at:`, title);
|
|
73
75
|
|
|
74
|
-
verbose_title = title;
|
|
76
|
+
// verbose_title = title;
|
|
75
77
|
|
|
76
78
|
try {
|
|
77
79
|
if (fileType === "image") await compressImage(filePath);
|
|
@@ -99,9 +101,9 @@ async function compressVideo(
|
|
|
99
101
|
resolution: Resolution,
|
|
100
102
|
force?: boolean,
|
|
101
103
|
) {
|
|
102
|
-
await
|
|
104
|
+
await ffmpeg_compress_mp4_gpu(
|
|
103
105
|
filePath,
|
|
104
|
-
|
|
106
|
+
addFilenameSuffix(filePath, ".compressed"),
|
|
105
107
|
resolution,
|
|
106
108
|
force,
|
|
107
109
|
);
|
package/src/lib/bash.ts
CHANGED
|
@@ -41,9 +41,6 @@ type GpuType = "nvidia" | "amd" | "intel" | "mac" | "cpu";
|
|
|
41
41
|
// Cache para não termos que detectar a GPU toda vez que formos comprimir um vídeo
|
|
42
42
|
let cachedGpuType: GpuType | null = null;
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
* Detecta a GPU instalada na máquina rodando comandos nativos do SO.
|
|
46
|
-
*/
|
|
47
44
|
async function detectGPU(): Promise<GpuType> {
|
|
48
45
|
if (cachedGpuType) return cachedGpuType;
|
|
49
46
|
const platform = os.platform();
|
|
@@ -73,59 +70,183 @@ async function detectGPU(): Promise<GpuType> {
|
|
|
73
70
|
return cachedGpuType;
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
export async function
|
|
73
|
+
export async function ffmpeg_video_to_webm(
|
|
77
74
|
input: string,
|
|
78
|
-
output: string,
|
|
79
|
-
resolution: Resolution = "480p",
|
|
80
|
-
forceResolution?: boolean,
|
|
75
|
+
output: string, // O arquivo deve terminar em .webm
|
|
81
76
|
) {
|
|
82
|
-
const resolutionMapping: { [key in Resolution]: { w: number; h: number } } = {
|
|
83
|
-
"4320p": { w: 7680, h: 4320 }, // 8K
|
|
84
|
-
"2160p": { w: 3840, h: 2160 }, // 4K
|
|
85
|
-
"1440p": { w: 2560, h: 1440 }, // Quad HD
|
|
86
|
-
"1080p": { w: 1920, h: 1080 }, // Full HD
|
|
87
|
-
"720p": { w: 1280, h: 720 }, // HD
|
|
88
|
-
"480p": { w: 854, h: 480 }, // SD (WVGA)
|
|
89
|
-
"360p": { w: 640, h: 360 }, // nHD
|
|
90
|
-
"240p": { w: 426, h: 240 }, // WQVGA
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const { w, h } = resolutionMapping[resolution];
|
|
94
77
|
const gpu = await detectGPU();
|
|
95
78
|
const isLinux = os.platform() === "linux";
|
|
96
79
|
|
|
97
|
-
//
|
|
80
|
+
// 1. Mapeia os Encoders VP9 para usar as GPUs
|
|
98
81
|
const encoders = {
|
|
99
82
|
nvidia: "-c:v vp9_nvenc -preset p4",
|
|
100
83
|
amd: isLinux ? "-c:v vp9_vaapi" : "-c:v vp9_amf",
|
|
101
84
|
intel: isLinux ? "-c:v vp9_vaapi" : "-c:v vp9_qsv",
|
|
102
|
-
mac: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
85
|
+
mac: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4", // Fallback (Mac não expõe VP9 nativo no FFmpeg)
|
|
103
86
|
cpu: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
104
87
|
};
|
|
105
88
|
|
|
106
89
|
const vcodec = encoders[gpu] || encoders.cpu;
|
|
107
90
|
|
|
108
|
-
let hwaccel =
|
|
91
|
+
let hwaccel = "";
|
|
109
92
|
let videoFilter = "";
|
|
110
93
|
|
|
94
|
+
// 2. Lógica VA-API (Linux) para manter todo o fluxo de frames na GPU
|
|
95
|
+
if (isLinux && (gpu === "amd" || gpu === "intel")) {
|
|
96
|
+
// Estas flags transferem a decodificação para a GPU e mantêm os frames na memória de vídeo
|
|
97
|
+
hwaccel =
|
|
98
|
+
"-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi";
|
|
99
|
+
// Como não vamos mudar a resolução, o filtro apenas formata e sobe para a GPU caso o input não venha em formato compatível
|
|
100
|
+
videoFilter = `-vf "format=nv12,hwupload"`;
|
|
101
|
+
} else if (gpu === "nvidia" || gpu === "amd" || gpu === "intel") {
|
|
102
|
+
hwaccel = "-hwaccel auto";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Áudio para WebM
|
|
106
|
+
const acodec = "-c:a libopus -b:a 128k";
|
|
107
|
+
|
|
108
|
+
// 4. Monta o comando (removido o -s e -filter:v de redimensionamento)
|
|
109
|
+
const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter ? videoFilter : ""} ${vcodec} ${acodec} -f webm -y "${output}"`;
|
|
110
|
+
|
|
111
|
+
console.log(`Compressing video [${gpu}]:`, input);
|
|
112
|
+
|
|
113
|
+
return execAsync(command);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// /**
|
|
117
|
+
// * Detecta a GPU instalada na máquina rodando comandos nativos do SO.
|
|
118
|
+
// */
|
|
119
|
+
// async function detectGPU(): Promise<GpuType> {
|
|
120
|
+
// if (cachedGpuType) return cachedGpuType;
|
|
121
|
+
// const platform = os.platform();
|
|
122
|
+
|
|
123
|
+
// try {
|
|
124
|
+
// if (platform === "darwin") return (cachedGpuType = "mac");
|
|
125
|
+
|
|
126
|
+
// let output = "";
|
|
127
|
+
// if (platform === "win32") {
|
|
128
|
+
// const stdout = await execAsync(
|
|
129
|
+
// "wmic path win32_VideoController get name",
|
|
130
|
+
// );
|
|
131
|
+
// output = stdout.toLowerCase();
|
|
132
|
+
// } else if (platform === "linux") {
|
|
133
|
+
// const stdout = await execAsync("lspci");
|
|
134
|
+
// output = stdout.toLowerCase();
|
|
135
|
+
// }
|
|
136
|
+
|
|
137
|
+
// if (output.includes("nvidia")) cachedGpuType = "nvidia";
|
|
138
|
+
// else if (output.includes("amd") || output.includes("radeon"))
|
|
139
|
+
// cachedGpuType = "amd";
|
|
140
|
+
// else if (output.includes("intel")) cachedGpuType = "intel";
|
|
141
|
+
// else cachedGpuType = "cpu";
|
|
142
|
+
// } catch (error) {
|
|
143
|
+
// cachedGpuType = "cpu";
|
|
144
|
+
// }
|
|
145
|
+
// return cachedGpuType;
|
|
146
|
+
// }
|
|
147
|
+
|
|
148
|
+
// export async function ffmpeg_video_compress_webm(
|
|
149
|
+
// input: string,
|
|
150
|
+
// output: string,
|
|
151
|
+
// resolution: Resolution = "480p",
|
|
152
|
+
// forceResolution?: boolean,
|
|
153
|
+
// ) {
|
|
154
|
+
// const { w, h } = resolutionMapping[resolution];
|
|
155
|
+
// const gpu = await detectGPU();
|
|
156
|
+
// const isLinux = os.platform() === "linux";
|
|
157
|
+
|
|
158
|
+
// // 1. Mapeia os Encoders para o codec VP9
|
|
159
|
+
// const encoders = {
|
|
160
|
+
// nvidia: "-c:v vp9_nvenc -preset p4",
|
|
161
|
+
// // Removemos o vp9_vaapi da AMD no Linux, caindo para a CPU (libvpx-vp9)
|
|
162
|
+
// amd: isLinux ? "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4" : "-c:v vp9_amf",
|
|
163
|
+
// intel: isLinux ? "-c:v vp9_vaapi" : "-c:v vp9_qsv", // Intel costuma suportar melhor vp9_vaapi, mas pode dar o mesmo erro dependendo da geração
|
|
164
|
+
// mac: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
165
|
+
// cpu: "-c:v libvpx-vp9 -row-mt 1 -cpu-used 4",
|
|
166
|
+
// };
|
|
167
|
+
|
|
168
|
+
// const vcodec = encoders[gpu] || encoders.cpu;
|
|
169
|
+
|
|
170
|
+
// // 2. Só ativamos o hwaccel se NÃO formos usar a CPU (libvpx-vp9)
|
|
171
|
+
// // Como a AMD no Linux agora usa libvpx-vp9, ela entra na regra de desativar o hwaccel para encode
|
|
172
|
+
// const isUsingCpuEncoder = vcodec.includes("libvpx");
|
|
173
|
+
// let hwaccel = isUsingCpuEncoder ? "" : "-hwaccel auto";
|
|
174
|
+
// let videoFilter = "";
|
|
175
|
+
|
|
176
|
+
// // 3. Lógica específica VA-API (agora só ativada se a Intel for usar hardware)
|
|
177
|
+
// if (isLinux && !isUsingCpuEncoder && gpu === "intel") {
|
|
178
|
+
// hwaccel = "-vaapi_device /dev/dri/renderD128";
|
|
179
|
+
// videoFilter = forceResolution
|
|
180
|
+
// ? `-vf "scale=${w}:${h},format=nv12,hwupload"`
|
|
181
|
+
// : `-vf "scale=-1:${h},format=nv12,hwupload"`;
|
|
182
|
+
// } else {
|
|
183
|
+
// // Escala normal via CPU (necessária para o libvpx-vp9)
|
|
184
|
+
// videoFilter = forceResolution ? `-s ${w}x${h}` : `-filter:v scale=-1:${h}`;
|
|
185
|
+
// }
|
|
186
|
+
|
|
187
|
+
// // O vídeo WebM necessita de codecs de áudio específicos como o Opus
|
|
188
|
+
// const acodec = "-c:a libopus -b:a 128k";
|
|
189
|
+
|
|
190
|
+
// // A flag '-f webm' força explicitamente a criação de um contentor de vídeo WebM
|
|
191
|
+
// const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter} ${vcodec} ${acodec} -f webm -y "${output}"`;
|
|
192
|
+
|
|
193
|
+
// return execAsync(command);
|
|
194
|
+
// }
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Comprime vídeo para MP4 (H.264) utilizando a GPU disponível com taxa de compressão otimizada.
|
|
198
|
+
*/
|
|
199
|
+
export async function ffmpeg_compress_mp4_gpu(
|
|
200
|
+
input: string,
|
|
201
|
+
output: string, // O arquivo deve terminar em .mp4
|
|
202
|
+
resolution: Resolution = "480p",
|
|
203
|
+
forceResolution?: boolean,
|
|
204
|
+
) {
|
|
205
|
+
const { w, h } = resolutionMapping[resolution];
|
|
206
|
+
const gpu = await detectGPU();
|
|
207
|
+
const isLinux = os.platform() === "linux";
|
|
208
|
+
|
|
209
|
+
// 1. Mapeia os Encoders H.264 com equivalência de compressão (Qualidade vs Tamanho)
|
|
210
|
+
// O valor 28 é o "sweet spot". Menor = mais qualidade/mais pesado. Maior = menos qualidade/mais leve.
|
|
211
|
+
const encoders = {
|
|
212
|
+
nvidia: "-c:v h264_nvenc -preset p4 -cq 28",
|
|
213
|
+
amd: isLinux ? "-c:v h264_vaapi -qp 28" : "-c:v h264_amf -qp_i 28 -qp_p 28",
|
|
214
|
+
intel: isLinux
|
|
215
|
+
? "-c:v h264_vaapi -qp 28"
|
|
216
|
+
: "-c:v h264_qsv -global_quality 28",
|
|
217
|
+
mac: "-c:v h264_videotoolbox -q:v 60", // No Mac a escala é diferente, 60 é um bom balanço
|
|
218
|
+
cpu: "-c:v libx264 -preset fast -crf 28",
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const vcodec = encoders[gpu] || encoders.cpu;
|
|
222
|
+
|
|
223
|
+
let hwaccel = gpu === "cpu" ? "" : "-hwaccel auto";
|
|
224
|
+
let videoFilter = "";
|
|
225
|
+
|
|
226
|
+
// 2. Define a string base do redimensionamento (Scale)
|
|
227
|
+
const scaleStr = forceResolution ? `scale=${w}:${h}` : `scale=-1:${h}`;
|
|
228
|
+
|
|
229
|
+
// 3. Aplica o filtro correto baseado na necessidade de VA-API no Linux
|
|
111
230
|
if (isLinux && (gpu === "amd" || gpu === "intel")) {
|
|
112
231
|
hwaccel = "-vaapi_device /dev/dri/renderD128";
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
: `-vf "scale=-1:${h},format=nv12,hwupload"`;
|
|
232
|
+
// Integra a resolução e o upload para a GPU no mesmo filtro
|
|
233
|
+
videoFilter = `-vf "${scaleStr},format=nv12,hwupload"`;
|
|
116
234
|
} else {
|
|
117
|
-
|
|
235
|
+
// Para NVIDIA, Mac e Windows, o scale simples via software antes de subir pra GPU funciona melhor
|
|
236
|
+
videoFilter = `-vf "${scaleStr}"`;
|
|
118
237
|
}
|
|
119
238
|
|
|
120
|
-
//
|
|
121
|
-
const acodec = "-c:a
|
|
239
|
+
// 4. Áudio AAC universal e forçamento do container MP4
|
|
240
|
+
const acodec = "-c:a aac -b:a 128k";
|
|
241
|
+
const format = "-f mp4";
|
|
122
242
|
|
|
123
|
-
//
|
|
124
|
-
const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter} ${vcodec} ${acodec}
|
|
243
|
+
// 5. Monta e executa o comando final
|
|
244
|
+
const command = `ffmpeg ${hwaccel} -i "${input}" ${videoFilter} ${vcodec} ${acodec} ${format} -y "${output}"`;
|
|
125
245
|
|
|
126
246
|
return execAsync(command);
|
|
127
247
|
}
|
|
128
248
|
|
|
129
249
|
export async function ffmpeg_image_to_webp(input: string, output: string) {
|
|
250
|
+
console.log(`Compressing image:`, input);
|
|
130
251
|
return execAsync(`ffmpeg -i "${input}" -c:v libwebp "${output}"`);
|
|
131
252
|
}
|
package/src/lib/constants.ts
CHANGED
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
export const resolutionMapping
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
},
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
},
|
|
14
|
-
"1080p": {
|
|
15
|
-
w: 1920,
|
|
16
|
-
h: 1080,
|
|
17
|
-
},
|
|
18
|
-
"720p": {
|
|
19
|
-
w: 1280,
|
|
20
|
-
h: 720,
|
|
21
|
-
},
|
|
22
|
-
"480p": {
|
|
23
|
-
w: 854,
|
|
24
|
-
h: 480,
|
|
25
|
-
},
|
|
26
|
-
"360p": {
|
|
27
|
-
w: 640,
|
|
28
|
-
h: 360,
|
|
29
|
-
},
|
|
30
|
-
"240p": {
|
|
31
|
-
w: 426,
|
|
32
|
-
h: 240,
|
|
33
|
-
},
|
|
34
|
-
} as const;
|
|
1
|
+
export const resolutionMapping: {
|
|
2
|
+
[key in Resolution]: { w: number; h: number };
|
|
3
|
+
} = {
|
|
4
|
+
"4320p": { w: 7680, h: 4320 }, // 8K
|
|
5
|
+
"2160p": { w: 3840, h: 2160 }, // 4K
|
|
6
|
+
"1440p": { w: 2560, h: 1440 }, // Quad HD
|
|
7
|
+
"1080p": { w: 1920, h: 1080 }, // Full HD
|
|
8
|
+
"720p": { w: 1280, h: 720 }, // HD
|
|
9
|
+
"480p": { w: 854, h: 480 }, // SD (WVGA)
|
|
10
|
+
"360p": { w: 640, h: 360 }, // nHD
|
|
11
|
+
"240p": { w: 426, h: 240 }, // WQVGA
|
|
12
|
+
};
|
|
35
13
|
|
|
36
14
|
export const Resolution = [
|
|
37
15
|
"4320p",
|
|
@@ -43,5 +21,4 @@ export const Resolution = [
|
|
|
43
21
|
"360p",
|
|
44
22
|
"240p",
|
|
45
23
|
] as const;
|
|
46
|
-
|
|
47
24
|
export type Resolution = (typeof Resolution)[number];
|