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