n8n-nodes-ffmpeg-wasm 1.2.8 → 1.3.0
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/dist/nodes/FFmpegWasm/FFmpegWasm.node.js +127 -782
- package/dist/nodes/FFmpegWasm/operations/advanced.d.ts +6 -0
- package/dist/nodes/FFmpegWasm/operations/advanced.js +219 -0
- package/dist/nodes/FFmpegWasm/operations/audio.d.ts +5 -0
- package/dist/nodes/FFmpegWasm/operations/audio.js +107 -0
- package/dist/nodes/FFmpegWasm/operations/convert.d.ts +3 -0
- package/dist/nodes/FFmpegWasm/operations/convert.js +39 -0
- package/dist/nodes/FFmpegWasm/operations/custom.d.ts +2 -0
- package/dist/nodes/FFmpegWasm/operations/custom.js +23 -0
- package/dist/nodes/FFmpegWasm/operations/index.d.ts +8 -0
- package/dist/nodes/FFmpegWasm/operations/index.js +32 -0
- package/dist/nodes/FFmpegWasm/operations/merge.d.ts +2 -0
- package/dist/nodes/FFmpegWasm/operations/merge.js +52 -0
- package/dist/nodes/FFmpegWasm/operations/metadata.d.ts +2 -0
- package/dist/nodes/FFmpegWasm/operations/metadata.js +17 -0
- package/dist/nodes/FFmpegWasm/operations/presets.d.ts +3 -0
- package/dist/nodes/FFmpegWasm/operations/presets.js +62 -0
- package/dist/nodes/FFmpegWasm/operations/video.d.ts +6 -0
- package/dist/nodes/FFmpegWasm/operations/video.js +144 -0
- package/dist/nodes/FFmpegWasm/types.d.ts +35 -0
- package/dist/nodes/FFmpegWasm/types.js +9 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FFmpegWasm = void 0;
|
|
4
4
|
const ffmpeg_1 = require("@ffmpeg/ffmpeg");
|
|
5
5
|
const helpers_1 = require("./helpers");
|
|
6
|
+
const operations_1 = require("./operations");
|
|
6
7
|
class FFmpegWasm {
|
|
7
8
|
constructor() {
|
|
8
9
|
this.description = {
|
|
@@ -952,6 +953,27 @@ class FFmpegWasm {
|
|
|
952
953
|
default: false,
|
|
953
954
|
description: "Whether to log FFmpeg output to console",
|
|
954
955
|
},
|
|
956
|
+
{
|
|
957
|
+
displayName: "Encoding Preset",
|
|
958
|
+
name: "encodingPreset",
|
|
959
|
+
type: "options",
|
|
960
|
+
default: "ultrafast",
|
|
961
|
+
description: "Encoding speed preset for x264/x265. Faster presets produce larger files but process much quicker in WebAssembly",
|
|
962
|
+
options: [
|
|
963
|
+
{
|
|
964
|
+
name: "Ultra Fast (fastest, larger files)",
|
|
965
|
+
value: "ultrafast",
|
|
966
|
+
},
|
|
967
|
+
{ name: "Super Fast", value: "superfast" },
|
|
968
|
+
{ name: "Very Fast", value: "veryfast" },
|
|
969
|
+
{ name: "Faster", value: "faster" },
|
|
970
|
+
{ name: "Fast", value: "fast" },
|
|
971
|
+
{
|
|
972
|
+
name: "Medium (FFmpeg default, slowest)",
|
|
973
|
+
value: "medium",
|
|
974
|
+
},
|
|
975
|
+
],
|
|
976
|
+
},
|
|
955
977
|
],
|
|
956
978
|
},
|
|
957
979
|
],
|
|
@@ -976,12 +998,14 @@ class FFmpegWasm {
|
|
|
976
998
|
}
|
|
977
999
|
catch {
|
|
978
1000
|
}
|
|
979
|
-
let lastLogOutput = "";
|
|
980
1001
|
const firstOpts = this.getNodeParameter("additionalOptions", 0, {});
|
|
1002
|
+
const logLines = [];
|
|
1003
|
+
const getLog = () => logLines.join("\n");
|
|
1004
|
+
const resetLog = () => { logLines.length = 0; };
|
|
981
1005
|
const ffmpeg = (0, ffmpeg_1.createFFmpeg)({
|
|
982
1006
|
log: firstOpts.enableLogging || false,
|
|
983
1007
|
logger: ({ message }) => {
|
|
984
|
-
|
|
1008
|
+
logLines.push(message);
|
|
985
1009
|
},
|
|
986
1010
|
...(corePath ? { corePath } : {}),
|
|
987
1011
|
});
|
|
@@ -1016,799 +1040,95 @@ class FFmpegWasm {
|
|
|
1016
1040
|
const outputFilename = `output_${i}_${Date.now()}`;
|
|
1017
1041
|
const inputData = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
|
1018
1042
|
ffmpeg.FS("writeFile", inputFilename, new Uint8Array(inputData));
|
|
1019
|
-
|
|
1020
|
-
|
|
1043
|
+
const preset = additionalOptions.encodingPreset || "ultrafast";
|
|
1044
|
+
const handlerParams = {
|
|
1045
|
+
ctx: this,
|
|
1046
|
+
ffmpeg,
|
|
1047
|
+
i,
|
|
1048
|
+
inputFilename,
|
|
1049
|
+
outputFilename,
|
|
1050
|
+
items,
|
|
1051
|
+
preset,
|
|
1052
|
+
getLog,
|
|
1053
|
+
resetLog,
|
|
1054
|
+
};
|
|
1021
1055
|
if (operation === "metadata") {
|
|
1022
|
-
|
|
1023
|
-
try {
|
|
1024
|
-
await Promise.race([
|
|
1025
|
-
ffmpeg.run("-i", inputFilename, "-hide_banner"),
|
|
1026
|
-
new Promise((resolve) => setTimeout(resolve, 10000)),
|
|
1027
|
-
]);
|
|
1028
|
-
}
|
|
1029
|
-
catch {
|
|
1030
|
-
}
|
|
1031
|
-
const metadata = (0, helpers_1.parseMetadataFromLogs)(lastLogOutput);
|
|
1056
|
+
const result = await (0, operations_1.handleMetadata)(handlerParams);
|
|
1032
1057
|
returnData.push({
|
|
1033
1058
|
json: {
|
|
1034
1059
|
...items[i].json,
|
|
1035
|
-
ffmpeg: { operation, ...metadata },
|
|
1060
|
+
ffmpeg: { operation, ...result.metadata },
|
|
1036
1061
|
},
|
|
1037
1062
|
});
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
catch { }
|
|
1063
|
+
safeUnlink(ffmpeg, inputFilename);
|
|
1064
|
+
for (const f of result.tempFiles)
|
|
1065
|
+
safeUnlink(ffmpeg, f);
|
|
1042
1066
|
continue;
|
|
1043
1067
|
}
|
|
1044
1068
|
const outputBinaryPropertyName = this.getNodeParameter("outputBinaryPropertyName", i);
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
"-vn",
|
|
1075
|
-
"-ar",
|
|
1076
|
-
"44100",
|
|
1077
|
-
"-ac",
|
|
1078
|
-
"2",
|
|
1079
|
-
"-b:a",
|
|
1080
|
-
audioQuality,
|
|
1081
|
-
"-y",
|
|
1082
|
-
outputName,
|
|
1083
|
-
];
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
case "resize": {
|
|
1087
|
-
const width = this.getNodeParameter("width", i);
|
|
1088
|
-
const height = this.getNodeParameter("height", i);
|
|
1089
|
-
const keepAspectRatio = this.getNodeParameter("keepAspectRatio", i);
|
|
1090
|
-
outputExt = ".mp4";
|
|
1091
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1092
|
-
const heightStr = keepAspectRatio
|
|
1093
|
-
? "-1"
|
|
1094
|
-
: height.toString();
|
|
1095
|
-
ffmpegCommand = [
|
|
1096
|
-
"-i",
|
|
1097
|
-
inputFilename,
|
|
1098
|
-
"-vf",
|
|
1099
|
-
`scale=${width}:${heightStr}`,
|
|
1100
|
-
"-c:a",
|
|
1101
|
-
"copy",
|
|
1102
|
-
"-y",
|
|
1103
|
-
outputName,
|
|
1104
|
-
];
|
|
1105
|
-
break;
|
|
1106
|
-
}
|
|
1107
|
-
case "thumbnail": {
|
|
1108
|
-
const timestamp = this.getNodeParameter("timestamp", i);
|
|
1109
|
-
const thumbnailWidth = this.getNodeParameter("thumbnailWidth", i);
|
|
1110
|
-
const thumbnailHeight = this.getNodeParameter("thumbnailHeight", i);
|
|
1111
|
-
outputExt = ".jpg";
|
|
1112
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1113
|
-
ffmpegCommand = [
|
|
1114
|
-
"-ss",
|
|
1115
|
-
timestamp,
|
|
1116
|
-
"-i",
|
|
1117
|
-
inputFilename,
|
|
1118
|
-
"-vframes",
|
|
1119
|
-
"1",
|
|
1120
|
-
"-q:v",
|
|
1121
|
-
"2",
|
|
1122
|
-
"-vf",
|
|
1123
|
-
`scale=${thumbnailWidth}:${thumbnailHeight}`,
|
|
1124
|
-
"-y",
|
|
1125
|
-
outputName,
|
|
1126
|
-
];
|
|
1127
|
-
break;
|
|
1128
|
-
}
|
|
1129
|
-
case "custom": {
|
|
1130
|
-
const ffmpegArgs = this.getNodeParameter("ffmpegArgs", i);
|
|
1131
|
-
const outputExtension = this.getNodeParameter("outputExtension", i);
|
|
1132
|
-
outputExt = (0, helpers_1.normalizeExtension)(outputExtension);
|
|
1133
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1134
|
-
const argsString = ffmpegArgs
|
|
1135
|
-
.replace(/\binput\b/g, inputFilename)
|
|
1136
|
-
.replace(/\boutput\b/g, outputName);
|
|
1137
|
-
ffmpegCommand = argsString
|
|
1138
|
-
.split(/\s+/)
|
|
1139
|
-
.filter((arg) => arg.length > 0);
|
|
1140
|
-
break;
|
|
1141
|
-
}
|
|
1142
|
-
case "merge": {
|
|
1143
|
-
const videoBinaryProperties = this.getNodeParameter("videoBinaryProperties", i);
|
|
1144
|
-
const mergeOutputFormat = this.getNodeParameter("mergeOutputFormat", i);
|
|
1145
|
-
const addTransition = this.getNodeParameter("addTransition", i);
|
|
1146
|
-
const binaryProps = videoBinaryProperties
|
|
1147
|
-
.split(",")
|
|
1148
|
-
.map((p) => p.trim())
|
|
1149
|
-
.filter((p) => p.length > 0);
|
|
1150
|
-
if (binaryProps.length < 2) {
|
|
1151
|
-
throw new Error("At least 2 video binary properties are required for merging");
|
|
1152
|
-
}
|
|
1153
|
-
const inputFiles = [];
|
|
1154
|
-
for (let j = 0; j < binaryProps.length; j++) {
|
|
1155
|
-
const propName = binaryProps[j];
|
|
1156
|
-
const propBinary = items[i].binary?.[propName];
|
|
1157
|
-
const ext = propBinary
|
|
1158
|
-
? (0, helpers_1.getInputExtension)(propBinary)
|
|
1159
|
-
: ".mp4";
|
|
1160
|
-
const videoData = await this.helpers.getBinaryDataBuffer(i, propName);
|
|
1161
|
-
const inputName = `input_${i}_${j}_${Date.now()}${ext}`;
|
|
1162
|
-
ffmpeg.FS("writeFile", inputName, new Uint8Array(videoData));
|
|
1163
|
-
inputFiles.push(inputName);
|
|
1164
|
-
}
|
|
1165
|
-
const concatList = inputFiles
|
|
1166
|
-
.map((f) => `file '${f}'`)
|
|
1167
|
-
.join("\n");
|
|
1168
|
-
const listFilename = `list_${i}_${Date.now()}.txt`;
|
|
1169
|
-
ffmpeg.FS("writeFile", listFilename, new TextEncoder().encode(concatList));
|
|
1170
|
-
outputExt = (0, helpers_1.normalizeExtension)(mergeOutputFormat);
|
|
1171
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1172
|
-
if (addTransition) {
|
|
1173
|
-
ffmpegCommand = [
|
|
1174
|
-
"-f",
|
|
1175
|
-
"concat",
|
|
1176
|
-
"-safe",
|
|
1177
|
-
"0",
|
|
1178
|
-
"-i",
|
|
1179
|
-
listFilename,
|
|
1180
|
-
"-vf",
|
|
1181
|
-
"fade=in:st=0:d=0.5,format=yuv420p",
|
|
1182
|
-
"-c:v",
|
|
1183
|
-
"libx264",
|
|
1184
|
-
"-preset",
|
|
1185
|
-
"fast",
|
|
1186
|
-
"-y",
|
|
1187
|
-
outputName,
|
|
1188
|
-
];
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
ffmpegCommand = [
|
|
1192
|
-
"-f",
|
|
1193
|
-
"concat",
|
|
1194
|
-
"-safe",
|
|
1195
|
-
"0",
|
|
1196
|
-
"-i",
|
|
1197
|
-
listFilename,
|
|
1198
|
-
"-c",
|
|
1199
|
-
"copy",
|
|
1200
|
-
"-y",
|
|
1201
|
-
outputName,
|
|
1202
|
-
];
|
|
1203
|
-
}
|
|
1204
|
-
break;
|
|
1205
|
-
}
|
|
1206
|
-
case "trim": {
|
|
1207
|
-
const startTime = this.getNodeParameter("startTime", i);
|
|
1208
|
-
const endTime = this.getNodeParameter("endTime", i);
|
|
1209
|
-
const duration = this.getNodeParameter("duration", i);
|
|
1210
|
-
outputExt = ".mp4";
|
|
1211
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1212
|
-
ffmpegCommand = ["-i", inputFilename, "-ss", startTime];
|
|
1213
|
-
if (duration) {
|
|
1214
|
-
ffmpegCommand.push("-t", duration);
|
|
1215
|
-
}
|
|
1216
|
-
else if (endTime) {
|
|
1217
|
-
ffmpegCommand.push("-to", endTime);
|
|
1218
|
-
}
|
|
1219
|
-
ffmpegCommand.push("-c", "copy", "-y", outputName);
|
|
1220
|
-
break;
|
|
1221
|
-
}
|
|
1222
|
-
case "videoFilters": {
|
|
1223
|
-
const brightness = this.getNodeParameter("brightness", i);
|
|
1224
|
-
const contrast = this.getNodeParameter("contrast", i);
|
|
1225
|
-
const saturation = this.getNodeParameter("saturation", i);
|
|
1226
|
-
const blur = this.getNodeParameter("blur", i);
|
|
1227
|
-
const grayscale = this.getNodeParameter("grayscale", i);
|
|
1228
|
-
const sepia = this.getNodeParameter("sepia", i);
|
|
1229
|
-
const filtersOutputFormat = this.getNodeParameter("filtersOutputFormat", i);
|
|
1230
|
-
const vfFilters = [];
|
|
1231
|
-
const eqParts = [];
|
|
1232
|
-
if (brightness !== 0)
|
|
1233
|
-
eqParts.push(`brightness=${brightness}`);
|
|
1234
|
-
if (contrast !== 1)
|
|
1235
|
-
eqParts.push(`contrast=${contrast}`);
|
|
1236
|
-
if (saturation !== 1)
|
|
1237
|
-
eqParts.push(`saturation=${saturation}`);
|
|
1238
|
-
if (eqParts.length > 0) {
|
|
1239
|
-
vfFilters.push(`eq=${eqParts.join(":")}`);
|
|
1240
|
-
}
|
|
1241
|
-
if (blur > 0) {
|
|
1242
|
-
vfFilters.push(`gblur=sigma=${blur}`);
|
|
1243
|
-
}
|
|
1244
|
-
if (grayscale) {
|
|
1245
|
-
vfFilters.push("format=gray");
|
|
1246
|
-
}
|
|
1247
|
-
if (sepia) {
|
|
1248
|
-
vfFilters.push("colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131");
|
|
1249
|
-
}
|
|
1250
|
-
outputExt = (0, helpers_1.normalizeExtension)(filtersOutputFormat);
|
|
1251
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1252
|
-
if (vfFilters.length > 0) {
|
|
1253
|
-
ffmpegCommand = [
|
|
1254
|
-
"-i",
|
|
1255
|
-
inputFilename,
|
|
1256
|
-
"-vf",
|
|
1257
|
-
vfFilters.join(","),
|
|
1258
|
-
"-c:a",
|
|
1259
|
-
"copy",
|
|
1260
|
-
"-y",
|
|
1261
|
-
outputName,
|
|
1262
|
-
];
|
|
1263
|
-
}
|
|
1264
|
-
else {
|
|
1265
|
-
ffmpegCommand = [
|
|
1266
|
-
"-i",
|
|
1267
|
-
inputFilename,
|
|
1268
|
-
"-c",
|
|
1269
|
-
"copy",
|
|
1270
|
-
"-y",
|
|
1271
|
-
outputName,
|
|
1272
|
-
];
|
|
1273
|
-
}
|
|
1274
|
-
break;
|
|
1275
|
-
}
|
|
1276
|
-
case "speed": {
|
|
1277
|
-
const speedValue = this.getNodeParameter("speedValue", i);
|
|
1278
|
-
const adjustAudioPitch = this.getNodeParameter("adjustAudioPitch", i);
|
|
1279
|
-
const speedOutputFormat = this.getNodeParameter("speedOutputFormat", i);
|
|
1280
|
-
const speed = parseFloat(speedValue);
|
|
1281
|
-
outputExt = (0, helpers_1.normalizeExtension)(speedOutputFormat);
|
|
1282
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1283
|
-
const videoFilter = `setpts=${1 / speed}*PTS`;
|
|
1284
|
-
if (adjustAudioPitch) {
|
|
1285
|
-
const audioFilter = (0, helpers_1.buildAtempoFilter)(speed);
|
|
1286
|
-
ffmpegCommand = [
|
|
1287
|
-
"-i",
|
|
1288
|
-
inputFilename,
|
|
1289
|
-
"-vf",
|
|
1290
|
-
videoFilter,
|
|
1291
|
-
"-af",
|
|
1292
|
-
audioFilter,
|
|
1293
|
-
"-y",
|
|
1294
|
-
outputName,
|
|
1295
|
-
];
|
|
1296
|
-
}
|
|
1297
|
-
else {
|
|
1298
|
-
ffmpegCommand = [
|
|
1299
|
-
"-i",
|
|
1300
|
-
inputFilename,
|
|
1301
|
-
"-vf",
|
|
1302
|
-
videoFilter,
|
|
1303
|
-
"-an",
|
|
1304
|
-
"-y",
|
|
1305
|
-
outputName,
|
|
1306
|
-
];
|
|
1307
|
-
}
|
|
1308
|
-
break;
|
|
1309
|
-
}
|
|
1310
|
-
case "rotate": {
|
|
1311
|
-
const rotation = this.getNodeParameter("rotation", i);
|
|
1312
|
-
const flipHorizontal = this.getNodeParameter("flipHorizontal", i);
|
|
1313
|
-
const flipVertical = this.getNodeParameter("flipVertical", i);
|
|
1314
|
-
const rotateOutputFormat = this.getNodeParameter("rotateOutputFormat", i);
|
|
1315
|
-
const vfParts = [];
|
|
1316
|
-
switch (rotation) {
|
|
1317
|
-
case "90":
|
|
1318
|
-
vfParts.push("transpose=1");
|
|
1319
|
-
break;
|
|
1320
|
-
case "270":
|
|
1321
|
-
vfParts.push("transpose=2");
|
|
1322
|
-
break;
|
|
1323
|
-
case "180":
|
|
1324
|
-
vfParts.push("transpose=1");
|
|
1325
|
-
vfParts.push("transpose=1");
|
|
1326
|
-
break;
|
|
1327
|
-
case "0":
|
|
1328
|
-
default:
|
|
1329
|
-
break;
|
|
1330
|
-
}
|
|
1331
|
-
if (flipHorizontal) {
|
|
1332
|
-
vfParts.push("hflip");
|
|
1333
|
-
}
|
|
1334
|
-
if (flipVertical) {
|
|
1335
|
-
vfParts.push("vflip");
|
|
1336
|
-
}
|
|
1337
|
-
outputExt = (0, helpers_1.normalizeExtension)(rotateOutputFormat);
|
|
1338
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1339
|
-
if (vfParts.length > 0) {
|
|
1340
|
-
ffmpegCommand = [
|
|
1341
|
-
"-i",
|
|
1342
|
-
inputFilename,
|
|
1343
|
-
"-vf",
|
|
1344
|
-
vfParts.join(","),
|
|
1345
|
-
"-c:a",
|
|
1346
|
-
"copy",
|
|
1347
|
-
"-y",
|
|
1348
|
-
outputName,
|
|
1349
|
-
];
|
|
1350
|
-
}
|
|
1351
|
-
else {
|
|
1352
|
-
ffmpegCommand = [
|
|
1353
|
-
"-i",
|
|
1354
|
-
inputFilename,
|
|
1355
|
-
"-c",
|
|
1356
|
-
"copy",
|
|
1357
|
-
"-y",
|
|
1358
|
-
outputName,
|
|
1359
|
-
];
|
|
1360
|
-
}
|
|
1361
|
-
break;
|
|
1362
|
-
}
|
|
1363
|
-
case "audioMix": {
|
|
1364
|
-
const audioBinaryProperties = this.getNodeParameter("audioBinaryProperties", i);
|
|
1365
|
-
const audioMixOutputFormat = this.getNodeParameter("audioMixOutputFormat", i);
|
|
1366
|
-
const binaryProps = audioBinaryProperties
|
|
1367
|
-
.split(",")
|
|
1368
|
-
.map((p) => p.trim())
|
|
1369
|
-
.filter((p) => p.length > 0);
|
|
1370
|
-
if (binaryProps.length < 2) {
|
|
1371
|
-
throw new Error("At least 2 audio binary properties are required for mixing");
|
|
1372
|
-
}
|
|
1373
|
-
const inputFiles = [];
|
|
1374
|
-
for (let j = 0; j < binaryProps.length; j++) {
|
|
1375
|
-
const propName = binaryProps[j];
|
|
1376
|
-
const propBinary = items[i].binary?.[propName];
|
|
1377
|
-
const ext = propBinary
|
|
1378
|
-
? (0, helpers_1.getInputExtension)(propBinary)
|
|
1379
|
-
: ".wav";
|
|
1380
|
-
const audioData = await this.helpers.getBinaryDataBuffer(i, propName);
|
|
1381
|
-
const inputName = `audio_input_${i}_${j}_${Date.now()}${ext}`;
|
|
1382
|
-
ffmpeg.FS("writeFile", inputName, new Uint8Array(audioData));
|
|
1383
|
-
inputFiles.push(inputName);
|
|
1384
|
-
}
|
|
1385
|
-
outputExt = (0, helpers_1.normalizeExtension)(audioMixOutputFormat);
|
|
1386
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1387
|
-
const filterComplex = inputFiles.map((_f, idx) => `[${idx}:a]`).join("") +
|
|
1388
|
-
`amix=inputs=${inputFiles.length}:duration=longest[aout]`;
|
|
1389
|
-
const inputs = [];
|
|
1390
|
-
for (const f of inputFiles) {
|
|
1391
|
-
inputs.push("-i", f);
|
|
1392
|
-
}
|
|
1393
|
-
ffmpegCommand = [
|
|
1394
|
-
...inputs,
|
|
1395
|
-
"-filter_complex",
|
|
1396
|
-
filterComplex,
|
|
1397
|
-
"-map",
|
|
1398
|
-
"[aout]",
|
|
1399
|
-
"-y",
|
|
1400
|
-
outputName,
|
|
1401
|
-
];
|
|
1402
|
-
break;
|
|
1403
|
-
}
|
|
1404
|
-
case "audioFilters": {
|
|
1405
|
-
const volume = this.getNodeParameter("volume", i);
|
|
1406
|
-
const bassBoost = this.getNodeParameter("bassBoost", i);
|
|
1407
|
-
const trebleBoost = this.getNodeParameter("trebleBoost", i);
|
|
1408
|
-
const highPass = this.getNodeParameter("highPass", i);
|
|
1409
|
-
const lowPass = this.getNodeParameter("lowPass", i);
|
|
1410
|
-
const audioFiltersOutputFormat = this.getNodeParameter("audioFiltersOutputFormat", i);
|
|
1411
|
-
const afFilters = [];
|
|
1412
|
-
if (volume !== 1.0)
|
|
1413
|
-
afFilters.push(`volume=${volume}`);
|
|
1414
|
-
if (bassBoost > 0)
|
|
1415
|
-
afFilters.push(`bass=g=${bassBoost}`);
|
|
1416
|
-
if (trebleBoost > 0)
|
|
1417
|
-
afFilters.push(`treble=g=${trebleBoost}`);
|
|
1418
|
-
if (highPass > 0)
|
|
1419
|
-
afFilters.push(`highpass=f=${highPass}`);
|
|
1420
|
-
if (lowPass > 0)
|
|
1421
|
-
afFilters.push(`lowpass=f=${lowPass}`);
|
|
1422
|
-
outputExt = (0, helpers_1.normalizeExtension)(audioFiltersOutputFormat);
|
|
1423
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1424
|
-
if (afFilters.length > 0) {
|
|
1425
|
-
ffmpegCommand = [
|
|
1426
|
-
"-i",
|
|
1427
|
-
inputFilename,
|
|
1428
|
-
"-af",
|
|
1429
|
-
afFilters.join(","),
|
|
1430
|
-
"-y",
|
|
1431
|
-
outputName,
|
|
1432
|
-
];
|
|
1433
|
-
}
|
|
1434
|
-
else {
|
|
1435
|
-
ffmpegCommand = [
|
|
1436
|
-
"-i",
|
|
1437
|
-
inputFilename,
|
|
1438
|
-
"-c:a",
|
|
1439
|
-
"copy",
|
|
1440
|
-
"-y",
|
|
1441
|
-
outputName,
|
|
1442
|
-
];
|
|
1443
|
-
}
|
|
1444
|
-
break;
|
|
1445
|
-
}
|
|
1446
|
-
case "audioNormalize": {
|
|
1447
|
-
const targetLoudness = this.getNodeParameter("targetLoudness", i);
|
|
1448
|
-
const truePeak = this.getNodeParameter("truePeak", i);
|
|
1449
|
-
const audioNormalizeOutputFormat = this.getNodeParameter("audioNormalizeOutputFormat", i);
|
|
1450
|
-
outputExt = (0, helpers_1.normalizeExtension)(audioNormalizeOutputFormat);
|
|
1451
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1452
|
-
ffmpegCommand = [
|
|
1453
|
-
"-i",
|
|
1454
|
-
inputFilename,
|
|
1455
|
-
"-af",
|
|
1456
|
-
`loudnorm=I=${targetLoudness}:TP=${truePeak}:LRA=11`,
|
|
1457
|
-
"-y",
|
|
1458
|
-
outputName,
|
|
1459
|
-
];
|
|
1460
|
-
break;
|
|
1461
|
-
}
|
|
1462
|
-
case "overlay": {
|
|
1463
|
-
const overlayBinaryProperty = this.getNodeParameter("overlayBinaryProperty", i);
|
|
1464
|
-
const overlayType = this.getNodeParameter("overlayType", i);
|
|
1465
|
-
const overlayX = this.getNodeParameter("overlayX", i);
|
|
1466
|
-
const overlayY = this.getNodeParameter("overlayY", i);
|
|
1467
|
-
const overlayWidth = this.getNodeParameter("overlayWidth", i);
|
|
1468
|
-
const overlayHeight = this.getNodeParameter("overlayHeight", i);
|
|
1469
|
-
const overlayOpacity = this.getNodeParameter("overlayOpacity", i);
|
|
1470
|
-
const overlayOutputFormat = this.getNodeParameter("overlayOutputFormat", i);
|
|
1471
|
-
const overlayBin = items[i].binary?.[overlayBinaryProperty];
|
|
1472
|
-
const overlayExt = overlayBin
|
|
1473
|
-
? (0, helpers_1.getInputExtension)(overlayBin)
|
|
1474
|
-
: ".png";
|
|
1475
|
-
const overlayData = await this.helpers.getBinaryDataBuffer(i, overlayBinaryProperty);
|
|
1476
|
-
const overlayFilename = `overlay_${i}_${Date.now()}${overlayExt}`;
|
|
1477
|
-
ffmpeg.FS("writeFile", overlayFilename, new Uint8Array(overlayData));
|
|
1478
|
-
outputExt = (0, helpers_1.normalizeExtension)(overlayOutputFormat);
|
|
1479
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1480
|
-
let videoFilter = "";
|
|
1481
|
-
if (overlayWidth > 0 || overlayHeight > 0) {
|
|
1482
|
-
const wStr = overlayWidth > 0
|
|
1483
|
-
? overlayWidth.toString()
|
|
1484
|
-
: "-1";
|
|
1485
|
-
const hStr = overlayHeight > 0
|
|
1486
|
-
? overlayHeight.toString()
|
|
1487
|
-
: "-1";
|
|
1488
|
-
videoFilter = `[1:v]scale=${wStr}:${hStr}`;
|
|
1489
|
-
if (overlayOpacity < 1.0) {
|
|
1490
|
-
videoFilter += `,format=rgba,colorchannelmixer=aa=${overlayOpacity}`;
|
|
1491
|
-
}
|
|
1492
|
-
videoFilter += `[ovrl];[0:v][ovrl]overlay=${overlayX}:${overlayY}[outv]`;
|
|
1493
|
-
}
|
|
1494
|
-
else {
|
|
1495
|
-
if (overlayOpacity < 1.0) {
|
|
1496
|
-
videoFilter = `[1:v]format=rgba,colorchannelmixer=aa=${overlayOpacity}[ovrl];[0:v][ovrl]overlay=${overlayX}:${overlayY}[outv]`;
|
|
1497
|
-
}
|
|
1498
|
-
else {
|
|
1499
|
-
videoFilter = `[0:v][1:v]overlay=${overlayX}:${overlayY}[outv]`;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
if (overlayType === "pip") {
|
|
1503
|
-
const fullFilter = `${videoFilter};[0:a][1:a]amix=inputs=2:duration=first[outa]`;
|
|
1504
|
-
ffmpegCommand = [
|
|
1505
|
-
"-i",
|
|
1506
|
-
inputFilename,
|
|
1507
|
-
"-i",
|
|
1508
|
-
overlayFilename,
|
|
1509
|
-
"-filter_complex",
|
|
1510
|
-
fullFilter,
|
|
1511
|
-
"-map",
|
|
1512
|
-
"[outv]",
|
|
1513
|
-
"-map",
|
|
1514
|
-
"[outa]",
|
|
1515
|
-
"-y",
|
|
1516
|
-
outputName,
|
|
1517
|
-
];
|
|
1518
|
-
}
|
|
1519
|
-
else {
|
|
1520
|
-
ffmpegCommand = [
|
|
1521
|
-
"-i",
|
|
1522
|
-
inputFilename,
|
|
1523
|
-
"-i",
|
|
1524
|
-
overlayFilename,
|
|
1525
|
-
"-filter_complex",
|
|
1526
|
-
videoFilter,
|
|
1527
|
-
"-map",
|
|
1528
|
-
"[outv]",
|
|
1529
|
-
"-map",
|
|
1530
|
-
"0:a?",
|
|
1531
|
-
"-c:a",
|
|
1532
|
-
"copy",
|
|
1533
|
-
"-y",
|
|
1534
|
-
outputName,
|
|
1535
|
-
];
|
|
1536
|
-
}
|
|
1537
|
-
break;
|
|
1538
|
-
}
|
|
1539
|
-
case "subtitle": {
|
|
1540
|
-
const subtitleBinaryProperty = this.getNodeParameter("subtitleBinaryProperty", i);
|
|
1541
|
-
const subtitleFormat = this.getNodeParameter("subtitleFormat", i);
|
|
1542
|
-
const subtitleFontSize = this.getNodeParameter("subtitleFontSize", i);
|
|
1543
|
-
const subtitleFontColor = this.getNodeParameter("subtitleFontColor", i);
|
|
1544
|
-
const subtitleBgOpacity = this.getNodeParameter("subtitleBgOpacity", i);
|
|
1545
|
-
const subtitlePosition = this.getNodeParameter("subtitlePosition", i);
|
|
1546
|
-
const subtitleOutputFormat = this.getNodeParameter("subtitleOutputFormat", i);
|
|
1547
|
-
const subtitleData = await this.helpers.getBinaryDataBuffer(i, subtitleBinaryProperty);
|
|
1548
|
-
const subtitleFilename = `subtitle_${i}_${Date.now()}.${subtitleFormat}`;
|
|
1549
|
-
ffmpeg.FS("writeFile", subtitleFilename, new Uint8Array(subtitleData));
|
|
1550
|
-
outputExt = (0, helpers_1.normalizeExtension)(subtitleOutputFormat);
|
|
1551
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1552
|
-
const assColor = (0, helpers_1.colorToAssFormat)(subtitleFontColor);
|
|
1553
|
-
let alignment = "2";
|
|
1554
|
-
if (subtitlePosition === "top") {
|
|
1555
|
-
alignment = "6";
|
|
1556
|
-
}
|
|
1557
|
-
else if (subtitlePosition === "center") {
|
|
1558
|
-
alignment = "5";
|
|
1559
|
-
}
|
|
1560
|
-
let subtitleFilter = "";
|
|
1561
|
-
if (subtitleBgOpacity > 0) {
|
|
1562
|
-
const alphaHex = Math.round(subtitleBgOpacity * 255)
|
|
1563
|
-
.toString(16)
|
|
1564
|
-
.padStart(2, "0")
|
|
1565
|
-
.toUpperCase();
|
|
1566
|
-
subtitleFilter = `subtitles=${subtitleFilename}:force_style='FontSize=${subtitleFontSize},PrimaryColour=${assColor},Alignment=${alignment},OutlineColour=&H00000000&,Outline=1,BorderStyle=4,BackColour=&H${alphaHex}000000&'`;
|
|
1567
|
-
}
|
|
1568
|
-
else {
|
|
1569
|
-
subtitleFilter = `subtitles=${subtitleFilename}:force_style='FontSize=${subtitleFontSize},PrimaryColour=${assColor},Alignment=${alignment}'`;
|
|
1570
|
-
}
|
|
1571
|
-
ffmpegCommand = [
|
|
1572
|
-
"-i",
|
|
1573
|
-
inputFilename,
|
|
1574
|
-
"-vf",
|
|
1575
|
-
subtitleFilter,
|
|
1576
|
-
"-c:a",
|
|
1577
|
-
"copy",
|
|
1578
|
-
"-y",
|
|
1579
|
-
outputName,
|
|
1580
|
-
];
|
|
1581
|
-
break;
|
|
1582
|
-
}
|
|
1583
|
-
case "gif": {
|
|
1584
|
-
const gifOutputFormat = this.getNodeParameter("gifOutputFormat", i);
|
|
1585
|
-
const gifWidth = this.getNodeParameter("gifWidth", i);
|
|
1586
|
-
const gifHeight = this.getNodeParameter("gifHeight", i);
|
|
1587
|
-
const gifFps = this.getNodeParameter("gifFps", i);
|
|
1588
|
-
const gifStartTime = this.getNodeParameter("gifStartTime", i);
|
|
1589
|
-
const gifDuration = this.getNodeParameter("gifDuration", i);
|
|
1590
|
-
const gifColors = this.getNodeParameter("gifColors", i);
|
|
1591
|
-
const gifDither = this.getNodeParameter("gifDither", i);
|
|
1592
|
-
const gifLoop = this.getNodeParameter("gifLoop", i);
|
|
1593
|
-
outputExt = `.${gifOutputFormat}`;
|
|
1594
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1595
|
-
const wStr = gifWidth > 0 ? gifWidth.toString() : "-1";
|
|
1596
|
-
const hStr = gifHeight > 0 ? gifHeight.toString() : "-1";
|
|
1597
|
-
const scaleFilter = `fps=${gifFps},scale=${wStr}:${hStr}:flags=lanczos`;
|
|
1598
|
-
if (gifOutputFormat === "gif") {
|
|
1599
|
-
const loopValue = gifLoop ? "0" : "-1";
|
|
1600
|
-
const gifFilter = `[0:v]${scaleFilter},split[s0][s1];[s0]palettegen=max_colors=${gifColors}[p];[s1][p]paletteuse=dither=${gifDither}`;
|
|
1601
|
-
ffmpegCommand = [
|
|
1602
|
-
"-ss",
|
|
1603
|
-
gifStartTime,
|
|
1604
|
-
"-t",
|
|
1605
|
-
gifDuration,
|
|
1606
|
-
"-i",
|
|
1607
|
-
inputFilename,
|
|
1608
|
-
"-filter_complex",
|
|
1609
|
-
gifFilter,
|
|
1610
|
-
"-loop",
|
|
1611
|
-
loopValue,
|
|
1612
|
-
"-y",
|
|
1613
|
-
outputName,
|
|
1614
|
-
];
|
|
1615
|
-
}
|
|
1616
|
-
else {
|
|
1617
|
-
const loopValue = gifLoop ? "0" : "1";
|
|
1618
|
-
ffmpegCommand = [
|
|
1619
|
-
"-ss",
|
|
1620
|
-
gifStartTime,
|
|
1621
|
-
"-t",
|
|
1622
|
-
gifDuration,
|
|
1623
|
-
"-i",
|
|
1624
|
-
inputFilename,
|
|
1625
|
-
"-vf",
|
|
1626
|
-
scaleFilter,
|
|
1627
|
-
"-loop",
|
|
1628
|
-
loopValue,
|
|
1629
|
-
"-y",
|
|
1630
|
-
outputName,
|
|
1631
|
-
];
|
|
1632
|
-
}
|
|
1633
|
-
break;
|
|
1634
|
-
}
|
|
1635
|
-
case "imageSequence": {
|
|
1636
|
-
const sequenceOutputFormat = this.getNodeParameter("sequenceOutputFormat", i);
|
|
1637
|
-
const sequenceWidth = this.getNodeParameter("sequenceWidth", i);
|
|
1638
|
-
const sequenceHeight = this.getNodeParameter("sequenceHeight", i);
|
|
1639
|
-
const sequenceFps = this.getNodeParameter("sequenceFps", i);
|
|
1640
|
-
const sequenceStartTime = this.getNodeParameter("sequenceStartTime", i);
|
|
1641
|
-
const sequenceDuration = this.getNodeParameter("sequenceDuration", i);
|
|
1642
|
-
const sequenceQuality = this.getNodeParameter("sequenceQuality", i);
|
|
1643
|
-
const outputPattern = `frame_%04d.${sequenceOutputFormat}`;
|
|
1644
|
-
const wStr = sequenceWidth > 0
|
|
1645
|
-
? sequenceWidth.toString()
|
|
1646
|
-
: "-1";
|
|
1647
|
-
const hStr = sequenceHeight > 0
|
|
1648
|
-
? sequenceHeight.toString()
|
|
1649
|
-
: "-1";
|
|
1650
|
-
const vfFilter = `fps=1/${sequenceFps},scale=${wStr}:${hStr}`;
|
|
1651
|
-
const cmdArgs = ["-ss", sequenceStartTime];
|
|
1652
|
-
if (sequenceDuration && sequenceDuration.length > 0) {
|
|
1653
|
-
cmdArgs.push("-t", sequenceDuration);
|
|
1654
|
-
}
|
|
1655
|
-
cmdArgs.push("-i", inputFilename, "-vf", vfFilter);
|
|
1656
|
-
if (sequenceOutputFormat === "jpg" ||
|
|
1657
|
-
sequenceOutputFormat === "jpeg") {
|
|
1658
|
-
cmdArgs.push("-q:v", Math.round(((100 - sequenceQuality) / 100) * 31).toString());
|
|
1659
|
-
}
|
|
1660
|
-
else if (sequenceOutputFormat === "webp") {
|
|
1661
|
-
cmdArgs.push("-q:v", sequenceQuality.toString());
|
|
1662
|
-
}
|
|
1663
|
-
cmdArgs.push("-y", outputPattern);
|
|
1664
|
-
ffmpegCommand = cmdArgs;
|
|
1665
|
-
const seqTimeoutMs = (additionalOptions.timeout || 300) * 1000;
|
|
1666
|
-
lastLogOutput = "";
|
|
1667
|
-
await Promise.race([
|
|
1668
|
-
ffmpeg.run(...ffmpegCommand),
|
|
1669
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`FFmpeg timed out after ${additionalOptions.timeout || 300}s`)), seqTimeoutMs)),
|
|
1670
|
-
]);
|
|
1671
|
-
const mimeType = (0, helpers_1.getMimeTypeFromExtension)(sequenceOutputFormat);
|
|
1672
|
-
let frameIndex = 1;
|
|
1673
|
-
while (true) {
|
|
1674
|
-
const frameName = `frame_${String(frameIndex).padStart(4, "0")}.${sequenceOutputFormat}`;
|
|
1675
|
-
try {
|
|
1676
|
-
const frameData = ffmpeg.FS("readFile", frameName);
|
|
1677
|
-
returnData.push({
|
|
1678
|
-
json: {
|
|
1679
|
-
...items[i].json,
|
|
1680
|
-
ffmpeg: {
|
|
1681
|
-
operation,
|
|
1682
|
-
inputFilename: binaryData.fileName || "input",
|
|
1683
|
-
outputFilename: frameName,
|
|
1684
|
-
frameIndex,
|
|
1685
|
-
size: frameData.length,
|
|
1686
|
-
},
|
|
1687
|
-
},
|
|
1688
|
-
binary: {
|
|
1689
|
-
[outputBinaryPropertyName]: {
|
|
1690
|
-
data: Buffer.from(frameData).toString("base64"),
|
|
1691
|
-
fileName: frameName,
|
|
1692
|
-
mimeType,
|
|
1693
|
-
},
|
|
1694
|
-
},
|
|
1695
|
-
});
|
|
1696
|
-
try {
|
|
1697
|
-
ffmpeg.FS("unlink", frameName);
|
|
1698
|
-
}
|
|
1699
|
-
catch { }
|
|
1700
|
-
frameIndex++;
|
|
1701
|
-
}
|
|
1702
|
-
catch {
|
|
1703
|
-
break;
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
try {
|
|
1707
|
-
ffmpeg.FS("unlink", inputFilename);
|
|
1708
|
-
}
|
|
1709
|
-
catch { }
|
|
1710
|
-
continue;
|
|
1069
|
+
if (operation === "imageSequence") {
|
|
1070
|
+
const result = await (0, operations_1.handleImageSequence)(handlerParams);
|
|
1071
|
+
const seqTimeoutMs = (additionalOptions.timeout || 300) * 1000;
|
|
1072
|
+
resetLog();
|
|
1073
|
+
let seqTimeoutId;
|
|
1074
|
+
await Promise.race([
|
|
1075
|
+
ffmpeg.run(...result.command),
|
|
1076
|
+
new Promise((_, reject) => {
|
|
1077
|
+
seqTimeoutId = setTimeout(() => reject(new Error(`FFmpeg timed out after ${additionalOptions.timeout || 300}s`)), seqTimeoutMs);
|
|
1078
|
+
}),
|
|
1079
|
+
]);
|
|
1080
|
+
clearTimeout(seqTimeoutId);
|
|
1081
|
+
const frameResults = (0, operations_1.readImageSequenceFrames)(ffmpeg, result.outputFormat, outputBinaryPropertyName, items[i].json, binaryData.fileName || "input");
|
|
1082
|
+
returnData.push(...frameResults);
|
|
1083
|
+
safeUnlink(ffmpeg, inputFilename);
|
|
1084
|
+
for (const f of result.tempFiles)
|
|
1085
|
+
safeUnlink(ffmpeg, f);
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
let commandResult;
|
|
1089
|
+
if (operation === "trim") {
|
|
1090
|
+
const startTime = this.getNodeParameter("startTime", i);
|
|
1091
|
+
const endTime = this.getNodeParameter("endTime", i);
|
|
1092
|
+
const duration = this.getNodeParameter("duration", i);
|
|
1093
|
+
const outputExt = ".mp4";
|
|
1094
|
+
const outputName = `${outputFilename}${outputExt}`;
|
|
1095
|
+
const command = ["-i", inputFilename, "-ss", startTime];
|
|
1096
|
+
if (duration) {
|
|
1097
|
+
command.push("-t", duration);
|
|
1711
1098
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
outputExt = (0, helpers_1.normalizeExtension)(remuxOutputFormat);
|
|
1715
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1716
|
-
ffmpegCommand = [
|
|
1717
|
-
"-i",
|
|
1718
|
-
inputFilename,
|
|
1719
|
-
"-c",
|
|
1720
|
-
"copy",
|
|
1721
|
-
"-y",
|
|
1722
|
-
outputName,
|
|
1723
|
-
];
|
|
1724
|
-
break;
|
|
1099
|
+
else if (endTime) {
|
|
1100
|
+
command.push("-to", endTime);
|
|
1725
1101
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
const outputName = `${outputFilename}${outputExt}`;
|
|
1734
|
-
ffmpegCommand = [
|
|
1735
|
-
"-i",
|
|
1736
|
-
inputFilename,
|
|
1737
|
-
"-vf",
|
|
1738
|
-
`scale=${preset.width}:${preset.height}:force_original_aspect_ratio=decrease,pad=${preset.width}:${preset.height}:(ow-iw)/2:(oh-ih)/2`,
|
|
1739
|
-
"-r",
|
|
1740
|
-
preset.fps.toString(),
|
|
1741
|
-
"-c:v",
|
|
1742
|
-
"libx264",
|
|
1743
|
-
"-b:v",
|
|
1744
|
-
preset.videoBitrate,
|
|
1745
|
-
"-c:a",
|
|
1746
|
-
"aac",
|
|
1747
|
-
"-b:a",
|
|
1748
|
-
preset.audioBitrate,
|
|
1749
|
-
"-movflags",
|
|
1750
|
-
"+faststart",
|
|
1751
|
-
];
|
|
1752
|
-
if (preset.maxDuration) {
|
|
1753
|
-
ffmpegCommand.push("-t", preset.maxDuration.toString());
|
|
1754
|
-
}
|
|
1755
|
-
ffmpegCommand.push("-y", outputName);
|
|
1756
|
-
break;
|
|
1102
|
+
command.push("-c", "copy", "-y", outputName);
|
|
1103
|
+
commandResult = { command, outputExt, tempFiles: [] };
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
const handler = OPERATION_HANDLERS[operation];
|
|
1107
|
+
if (!handler) {
|
|
1108
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
1757
1109
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
try {
|
|
1766
|
-
await Promise.race([
|
|
1767
|
-
ffmpeg.run("-i", inputFilename, "-hide_banner"),
|
|
1768
|
-
new Promise((resolve) => setTimeout(resolve, 10000)),
|
|
1769
|
-
]);
|
|
1770
|
-
}
|
|
1771
|
-
catch {
|
|
1772
|
-
}
|
|
1773
|
-
const meta = (0, helpers_1.parseMetadataFromLogs)(lastLogOutput);
|
|
1774
|
-
const durationSec = meta.durationSeconds || 60;
|
|
1775
|
-
const audioBitrateKbps = parseInt(compressAudioBitrate) || 128;
|
|
1776
|
-
const targetBitsPerSec = (targetSizeMB * 8 * 1024 * 1024) / durationSec;
|
|
1777
|
-
const videoBitrate = Math.max(100, Math.floor(targetBitsPerSec / 1000 - audioBitrateKbps));
|
|
1778
|
-
ffmpegCommand = [
|
|
1779
|
-
"-i",
|
|
1780
|
-
inputFilename,
|
|
1781
|
-
"-c:v",
|
|
1782
|
-
"libx264",
|
|
1783
|
-
"-b:v",
|
|
1784
|
-
`${videoBitrate}k`,
|
|
1785
|
-
"-maxrate",
|
|
1786
|
-
`${Math.floor(videoBitrate * 1.5)}k`,
|
|
1787
|
-
"-bufsize",
|
|
1788
|
-
`${videoBitrate * 2}k`,
|
|
1789
|
-
"-c:a",
|
|
1790
|
-
"aac",
|
|
1791
|
-
"-b:a",
|
|
1792
|
-
compressAudioBitrate,
|
|
1793
|
-
"-movflags",
|
|
1794
|
-
"+faststart",
|
|
1795
|
-
"-y",
|
|
1796
|
-
outputName,
|
|
1797
|
-
];
|
|
1798
|
-
break;
|
|
1110
|
+
commandResult = await handler(handlerParams);
|
|
1111
|
+
}
|
|
1112
|
+
if (commandResult.outputExt === ".mp4" &&
|
|
1113
|
+
!commandResult.command.includes("-movflags")) {
|
|
1114
|
+
const yIdx = commandResult.command.indexOf("-y");
|
|
1115
|
+
if (yIdx !== -1) {
|
|
1116
|
+
commandResult.command.splice(yIdx, 0, "-movflags", "+faststart");
|
|
1799
1117
|
}
|
|
1800
|
-
default:
|
|
1801
|
-
throw new Error(`Unknown operation: ${operation}`);
|
|
1802
1118
|
}
|
|
1803
1119
|
const timeoutMs = (additionalOptions.timeout || 300) * 1000;
|
|
1804
|
-
|
|
1120
|
+
resetLog();
|
|
1121
|
+
let timeoutId;
|
|
1805
1122
|
await Promise.race([
|
|
1806
|
-
ffmpeg.run(...
|
|
1807
|
-
new Promise((_, reject) =>
|
|
1123
|
+
ffmpeg.run(...commandResult.command),
|
|
1124
|
+
new Promise((_, reject) => {
|
|
1125
|
+
timeoutId = setTimeout(() => reject(new Error(`FFmpeg timed out after ${additionalOptions.timeout || 300}s`)), timeoutMs);
|
|
1126
|
+
}),
|
|
1808
1127
|
]);
|
|
1809
|
-
|
|
1128
|
+
clearTimeout(timeoutId);
|
|
1129
|
+
const outputName = `${outputFilename}${commandResult.outputExt}`;
|
|
1810
1130
|
const outputData = ffmpeg.FS("readFile", outputName);
|
|
1811
|
-
const outputMimeType = (0, helpers_1.getMimeTypeFromExtension)(outputExt.replace(".", ""));
|
|
1131
|
+
const outputMimeType = (0, helpers_1.getMimeTypeFromExtension)(commandResult.outputExt.replace(".", ""));
|
|
1812
1132
|
const outputItem = {
|
|
1813
1133
|
json: {
|
|
1814
1134
|
...items[i].json,
|
|
@@ -1828,11 +1148,10 @@ class FFmpegWasm {
|
|
|
1828
1148
|
},
|
|
1829
1149
|
};
|
|
1830
1150
|
returnData.push(outputItem);
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
catch { }
|
|
1151
|
+
safeUnlink(ffmpeg, inputFilename);
|
|
1152
|
+
safeUnlink(ffmpeg, outputName);
|
|
1153
|
+
for (const f of commandResult.tempFiles)
|
|
1154
|
+
safeUnlink(ffmpeg, f);
|
|
1836
1155
|
}
|
|
1837
1156
|
catch (error) {
|
|
1838
1157
|
if (this.continueOnFail()) {
|
|
@@ -1865,3 +1184,29 @@ class FFmpegWasm {
|
|
|
1865
1184
|
}
|
|
1866
1185
|
}
|
|
1867
1186
|
exports.FFmpegWasm = FFmpegWasm;
|
|
1187
|
+
function safeUnlink(ffmpeg, filename) {
|
|
1188
|
+
try {
|
|
1189
|
+
ffmpeg.FS("unlink", filename);
|
|
1190
|
+
}
|
|
1191
|
+
catch { }
|
|
1192
|
+
}
|
|
1193
|
+
const OPERATION_HANDLERS = {
|
|
1194
|
+
convert: operations_1.handleConvert,
|
|
1195
|
+
extractAudio: operations_1.handleExtractAudio,
|
|
1196
|
+
resize: operations_1.handleResize,
|
|
1197
|
+
thumbnail: operations_1.handleThumbnail,
|
|
1198
|
+
custom: operations_1.handleCustom,
|
|
1199
|
+
merge: operations_1.handleMerge,
|
|
1200
|
+
videoFilters: operations_1.handleVideoFilters,
|
|
1201
|
+
speed: operations_1.handleSpeed,
|
|
1202
|
+
rotate: operations_1.handleRotate,
|
|
1203
|
+
audioMix: operations_1.handleAudioMix,
|
|
1204
|
+
audioFilters: operations_1.handleAudioFilters,
|
|
1205
|
+
audioNormalize: operations_1.handleAudioNormalize,
|
|
1206
|
+
overlay: operations_1.handleOverlay,
|
|
1207
|
+
subtitle: operations_1.handleSubtitle,
|
|
1208
|
+
gif: operations_1.handleGif,
|
|
1209
|
+
remux: operations_1.handleRemux,
|
|
1210
|
+
socialMedia: operations_1.handleSocialMedia,
|
|
1211
|
+
compressToSize: operations_1.handleCompressToSize,
|
|
1212
|
+
};
|