@gannochenko/staticstripes 0.0.1

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.
Files changed (86) hide show
  1. package/.prettierrc +8 -0
  2. package/Makefile +69 -0
  3. package/dist/asset-manager.d.ts +16 -0
  4. package/dist/asset-manager.d.ts.map +1 -0
  5. package/dist/asset-manager.js +50 -0
  6. package/dist/asset-manager.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +257 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/container-renderer.d.ts +21 -0
  12. package/dist/container-renderer.d.ts.map +1 -0
  13. package/dist/container-renderer.js +149 -0
  14. package/dist/container-renderer.js.map +1 -0
  15. package/dist/expression-parser.d.ts +63 -0
  16. package/dist/expression-parser.d.ts.map +1 -0
  17. package/dist/expression-parser.js +145 -0
  18. package/dist/expression-parser.js.map +1 -0
  19. package/dist/ffmpeg.d.ts +375 -0
  20. package/dist/ffmpeg.d.ts.map +1 -0
  21. package/dist/ffmpeg.js +997 -0
  22. package/dist/ffmpeg.js.map +1 -0
  23. package/dist/ffprobe.d.ts +2 -0
  24. package/dist/ffprobe.d.ts.map +1 -0
  25. package/dist/ffprobe.js +31 -0
  26. package/dist/ffprobe.js.map +1 -0
  27. package/dist/html-parser.d.ts +56 -0
  28. package/dist/html-parser.d.ts.map +1 -0
  29. package/dist/html-parser.js +208 -0
  30. package/dist/html-parser.js.map +1 -0
  31. package/dist/html-project-parser.d.ts +169 -0
  32. package/dist/html-project-parser.d.ts.map +1 -0
  33. package/dist/html-project-parser.js +954 -0
  34. package/dist/html-project-parser.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/label-generator.d.ts +35 -0
  40. package/dist/label-generator.d.ts.map +1 -0
  41. package/dist/label-generator.js +69 -0
  42. package/dist/label-generator.js.map +1 -0
  43. package/dist/project.d.ts +29 -0
  44. package/dist/project.d.ts.map +1 -0
  45. package/dist/project.js +137 -0
  46. package/dist/project.js.map +1 -0
  47. package/dist/sample-sequences.d.ts +5 -0
  48. package/dist/sample-sequences.d.ts.map +1 -0
  49. package/dist/sample-sequences.js +199 -0
  50. package/dist/sample-sequences.js.map +1 -0
  51. package/dist/sample-streams.d.ts +2 -0
  52. package/dist/sample-streams.d.ts.map +1 -0
  53. package/dist/sample-streams.js +109 -0
  54. package/dist/sample-streams.js.map +1 -0
  55. package/dist/sequence.d.ts +21 -0
  56. package/dist/sequence.d.ts.map +1 -0
  57. package/dist/sequence.js +269 -0
  58. package/dist/sequence.js.map +1 -0
  59. package/dist/stream.d.ts +135 -0
  60. package/dist/stream.d.ts.map +1 -0
  61. package/dist/stream.js +779 -0
  62. package/dist/stream.js.map +1 -0
  63. package/dist/type.d.ts +73 -0
  64. package/dist/type.d.ts.map +1 -0
  65. package/dist/type.js +3 -0
  66. package/dist/type.js.map +1 -0
  67. package/eslint.config.js +44 -0
  68. package/package.json +50 -0
  69. package/src/asset-manager.ts +55 -0
  70. package/src/cli.ts +306 -0
  71. package/src/container-renderer.ts +190 -0
  72. package/src/expression-parser.test.ts +459 -0
  73. package/src/expression-parser.ts +199 -0
  74. package/src/ffmpeg.ts +1403 -0
  75. package/src/ffprobe.ts +29 -0
  76. package/src/html-parser.ts +221 -0
  77. package/src/html-project-parser.ts +1195 -0
  78. package/src/index.ts +9 -0
  79. package/src/label-generator.ts +74 -0
  80. package/src/project.ts +180 -0
  81. package/src/sample-sequences.ts +225 -0
  82. package/src/sample-streams.ts +142 -0
  83. package/src/sequence.ts +330 -0
  84. package/src/stream.ts +1012 -0
  85. package/src/type.ts +81 -0
  86. package/tsconfig.json +24 -0
package/dist/stream.js ADDED
@@ -0,0 +1,779 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Stream = exports.FilterBuffer = exports.VisualFilter = exports.Colors = exports.ChromakeyBlend = exports.ChromakeySimilarity = exports.Direction = exports.AMBIENT = exports.PILLARBOX = void 0;
4
+ exports.makeStream = makeStream;
5
+ exports.makeSilentStream = makeSilentStream;
6
+ exports.makeBlankStream = makeBlankStream;
7
+ const ffmpeg_1 = require("./ffmpeg");
8
+ exports.PILLARBOX = 'pillarbox';
9
+ exports.AMBIENT = 'ambient';
10
+ var Direction;
11
+ (function (Direction) {
12
+ Direction[Direction["CW"] = 0] = "CW";
13
+ Direction[Direction["CW2"] = 1] = "CW2";
14
+ Direction[Direction["CCW"] = 2] = "CCW";
15
+ Direction[Direction["CCW2"] = 3] = "CCW2";
16
+ })(Direction || (exports.Direction = Direction = {}));
17
+ var ChromakeySimilarity;
18
+ (function (ChromakeySimilarity) {
19
+ ChromakeySimilarity[ChromakeySimilarity["Strict"] = 0.1] = "Strict";
20
+ ChromakeySimilarity[ChromakeySimilarity["Good"] = 0.3] = "Good";
21
+ ChromakeySimilarity[ChromakeySimilarity["Forgiving"] = 0.5] = "Forgiving";
22
+ ChromakeySimilarity[ChromakeySimilarity["Loose"] = 0.7] = "Loose";
23
+ })(ChromakeySimilarity || (exports.ChromakeySimilarity = ChromakeySimilarity = {}));
24
+ var ChromakeyBlend;
25
+ (function (ChromakeyBlend) {
26
+ ChromakeyBlend[ChromakeyBlend["Hard"] = 0] = "Hard";
27
+ ChromakeyBlend[ChromakeyBlend["Smooth"] = 0.1] = "Smooth";
28
+ ChromakeyBlend[ChromakeyBlend["Soft"] = 0.2] = "Soft";
29
+ })(ChromakeyBlend || (exports.ChromakeyBlend = ChromakeyBlend = {}));
30
+ var Colors;
31
+ (function (Colors) {
32
+ Colors["Transparent"] = "#00000000";
33
+ })(Colors || (exports.Colors = Colors = {}));
34
+ var VisualFilter;
35
+ (function (VisualFilter) {
36
+ VisualFilter["InstagramClarendon"] = "instagram-clarendon";
37
+ VisualFilter["InstagramGingham"] = "instagram-gingham";
38
+ VisualFilter["InstagramJuno"] = "instagram-juno";
39
+ VisualFilter["InstagramLark"] = "instagram-lark";
40
+ VisualFilter["InstagramLudwig"] = "instagram-ludwig";
41
+ VisualFilter["InstagramNashville"] = "instagram-nashville";
42
+ VisualFilter["InstagramValencia"] = "instagram-valencia";
43
+ VisualFilter["InstagramXProII"] = "instagram-xpro2";
44
+ VisualFilter["InstagramWillow"] = "instagram-willow";
45
+ VisualFilter["InstagramLoFi"] = "instagram-lofi";
46
+ VisualFilter["InstagramInkwell"] = "instagram-inkwell";
47
+ VisualFilter["InstagramMoon"] = "instagram-moon";
48
+ VisualFilter["InstagramHudson"] = "instagram-hudson";
49
+ VisualFilter["InstagramToaster"] = "instagram-toaster";
50
+ VisualFilter["InstagramWalden"] = "instagram-walden";
51
+ VisualFilter["InstagramRise"] = "instagram-rise";
52
+ VisualFilter["InstagramAmaro"] = "instagram-amaro";
53
+ VisualFilter["InstagramMayfair"] = "instagram-mayfair";
54
+ VisualFilter["InstagramEarlybird"] = "instagram-earlybird";
55
+ VisualFilter["InstagramSutro"] = "instagram-sutro";
56
+ VisualFilter["InstagramAden"] = "instagram-aden";
57
+ VisualFilter["InstagramCrema"] = "instagram-crema";
58
+ })(VisualFilter || (exports.VisualFilter = VisualFilter = {}));
59
+ class FilterBuffer {
60
+ filters = [];
61
+ append(filter) {
62
+ this.filters.push(filter);
63
+ }
64
+ render() {
65
+ return this.filters.map((filter) => filter.render()).join(';');
66
+ }
67
+ }
68
+ exports.FilterBuffer = FilterBuffer;
69
+ function makeStream(label, buf) {
70
+ return new Stream(label, buf);
71
+ }
72
+ function makeSilentStream(duration, buf) {
73
+ const filter = (0, ffmpeg_1.makeAnullsrc)({ duration });
74
+ buf.append(filter);
75
+ return new Stream(filter.outputs[0], buf);
76
+ }
77
+ function makeBlankStream(duration, width, height, fps, buf) {
78
+ const filter = (0, ffmpeg_1.makeColor)({
79
+ duration,
80
+ width,
81
+ height,
82
+ fps,
83
+ color: '#00000000',
84
+ });
85
+ buf.append(filter);
86
+ return new Stream(filter.outputs[0], buf);
87
+ }
88
+ class Stream {
89
+ looseEnd;
90
+ buf;
91
+ constructor(looseEnd, buf) {
92
+ this.looseEnd = looseEnd;
93
+ this.buf = buf;
94
+ }
95
+ trim(start, end) {
96
+ const res = (0, ffmpeg_1.makeTrim)([this.looseEnd], start, end);
97
+ this.looseEnd = res.outputs[0];
98
+ this.buf.append(res);
99
+ return this;
100
+ }
101
+ fitOutputSimple(dimensions) {
102
+ // Step 1: Scale video to fit within dimensions while maintaining aspect ratio
103
+ // Using 'force_original_aspect_ratio=decrease' ensures the video fits inside the box
104
+ const scaleRes = (0, ffmpeg_1.makeScale)([this.looseEnd], {
105
+ width: dimensions.width,
106
+ height: dimensions.height,
107
+ flags: 'force_original_aspect_ratio=decrease',
108
+ });
109
+ this.looseEnd = scaleRes.outputs[0];
110
+ this.buf.append(scaleRes);
111
+ // Step 2: Pad to exact dimensions with black bars (centered)
112
+ const padRes = (0, ffmpeg_1.makePad)([this.looseEnd], {
113
+ width: dimensions.width,
114
+ height: dimensions.height,
115
+ // x and y default to '(ow-iw)/2' and '(oh-ih)/2' which centers the video
116
+ });
117
+ this.looseEnd = padRes.outputs[0];
118
+ this.buf.append(padRes);
119
+ return this;
120
+ }
121
+ fitOutputCover(dimensions) {
122
+ // Step 1: Scale video to cover dimensions while maintaining aspect ratio
123
+ // Using 'force_original_aspect_ratio=increase' ensures the video fills the entire box
124
+ const scaleRes = (0, ffmpeg_1.makeScale)([this.looseEnd], {
125
+ width: dimensions.width,
126
+ height: dimensions.height,
127
+ flags: 'force_original_aspect_ratio=increase',
128
+ });
129
+ this.looseEnd = scaleRes.outputs[0];
130
+ this.buf.append(scaleRes);
131
+ // Step 2: Crop to exact dimensions (centered)
132
+ const cropRes = (0, ffmpeg_1.makeCrop)([this.looseEnd], {
133
+ width: dimensions.width,
134
+ height: dimensions.height,
135
+ // x and y default to '(in_w-out_w)/2' and '(in_h-out_h)/2' which centers the crop
136
+ });
137
+ this.looseEnd = cropRes.outputs[0];
138
+ this.buf.append(cropRes);
139
+ return this;
140
+ }
141
+ fitOutputContain(dimensions, options = {}) {
142
+ if (options.ambient) {
143
+ const blurStrength = options.ambient?.blurStrength ?? 20;
144
+ const brightness = options.ambient?.brightness ?? -0.3;
145
+ const saturation = options.ambient?.saturation ?? 0.8;
146
+ // Split input into 2 streams: background and foreground
147
+ const splitRes = (0, ffmpeg_1.makeSplit)([this.looseEnd]);
148
+ this.buf.append(splitRes);
149
+ const [bgLabel, fgLabel] = splitRes.outputs;
150
+ // // Background stream: cover + blur + darken
151
+ const bgScaleRes = (0, ffmpeg_1.makeScale)([bgLabel], {
152
+ width: dimensions.width,
153
+ height: dimensions.height,
154
+ flags: 'force_original_aspect_ratio=increase',
155
+ });
156
+ this.buf.append(bgScaleRes);
157
+ const bgCropRes = (0, ffmpeg_1.makeCrop)(bgScaleRes.outputs, {
158
+ width: dimensions.width,
159
+ height: dimensions.height,
160
+ });
161
+ this.buf.append(bgCropRes);
162
+ const bgBlurRes = (0, ffmpeg_1.makeGblur)(bgCropRes.outputs, {
163
+ sigma: blurStrength,
164
+ steps: 2,
165
+ });
166
+ this.buf.append(bgBlurRes);
167
+ const bgFinal = (0, ffmpeg_1.makeEq)(bgBlurRes.outputs, {
168
+ brightness,
169
+ saturation,
170
+ });
171
+ this.buf.append(bgFinal);
172
+ ////////////////////////////////////////////////////////////////////////////////////
173
+ const fgScale = (0, ffmpeg_1.makeScale)([fgLabel], {
174
+ width: dimensions.width,
175
+ height: dimensions.height,
176
+ flags: 'force_original_aspect_ratio=decrease',
177
+ });
178
+ this.buf.append(fgScale);
179
+ // Step 2: Pad to exact dimensions with black bars (centered)
180
+ const fgFinal = (0, ffmpeg_1.makePad)(fgScale.outputs, {
181
+ width: dimensions.width,
182
+ height: dimensions.height,
183
+ color: '#00000000', // transparent
184
+ // x and y default to '(ow-iw)/2' and '(oh-ih)/2' which centers the video
185
+ });
186
+ this.buf.append(fgFinal);
187
+ ////////////////////////////////////////////////////////////////////////////////////
188
+ // Overlay foreground centered on background
189
+ // (W-w)/2 and (H-h)/2 center the overlay on the background
190
+ const overlayRes = (0, ffmpeg_1.makeOverlay)([bgFinal.outputs[0], fgFinal.outputs[0]], {
191
+ x: '(W-w)/2',
192
+ y: '(H-h)/2',
193
+ });
194
+ this.buf.append(overlayRes);
195
+ this.looseEnd = overlayRes.outputs[0];
196
+ }
197
+ else {
198
+ // usual pillarbox
199
+ const color = options?.pillarbox?.color ?? '#000000';
200
+ const scaleRes = (0, ffmpeg_1.makeScale)([this.looseEnd], {
201
+ width: dimensions.width,
202
+ height: dimensions.height,
203
+ flags: 'force_original_aspect_ratio=decrease',
204
+ });
205
+ this.looseEnd = scaleRes.outputs[0];
206
+ this.buf.append(scaleRes);
207
+ // Step 2: Pad to exact dimensions with black bars (centered)
208
+ const padRes = (0, ffmpeg_1.makePad)([this.looseEnd], {
209
+ width: dimensions.width,
210
+ height: dimensions.height,
211
+ color: color,
212
+ // x and y default to '(ow-iw)/2' and '(oh-ih)/2' which centers the video
213
+ });
214
+ this.looseEnd = padRes.outputs[0];
215
+ this.buf.append(padRes);
216
+ }
217
+ return this;
218
+ }
219
+ chromakey(parameters) {
220
+ // Apply chromakey filter
221
+ const chromakeyRes = (0, ffmpeg_1.makeChromakey)([this.looseEnd], {
222
+ color: parameters.color,
223
+ similarity: parameters.similarity,
224
+ blend: parameters.blend,
225
+ });
226
+ this.looseEnd = chromakeyRes.outputs[0];
227
+ this.buf.append(chromakeyRes);
228
+ return this;
229
+ }
230
+ fps(value) {
231
+ const res = (0, ffmpeg_1.makeFps)([this.looseEnd], value);
232
+ this.looseEnd = res.outputs[0];
233
+ this.buf.append(res);
234
+ return this;
235
+ }
236
+ blur(strength) {
237
+ const res = (0, ffmpeg_1.makeGblur)([this.looseEnd], {
238
+ sigma: strength,
239
+ });
240
+ this.looseEnd = res.outputs[0];
241
+ this.buf.append(res);
242
+ return this;
243
+ }
244
+ fade(options) {
245
+ const res = (0, ffmpeg_1.makeFade)([this.looseEnd], options);
246
+ this.looseEnd = res.outputs[0];
247
+ this.buf.append(res);
248
+ return this;
249
+ }
250
+ transpose(value) {
251
+ const res = (0, ffmpeg_1.makeTranspose)([this.looseEnd], value);
252
+ this.looseEnd = res.outputs[0];
253
+ this.buf.append(res);
254
+ return this;
255
+ }
256
+ cwRotate(direction) {
257
+ switch (direction) {
258
+ case Direction.CW:
259
+ // 90° clockwise: transpose=1
260
+ this.transpose(1);
261
+ break;
262
+ case Direction.CCW:
263
+ // 90° counterclockwise: transpose=2
264
+ this.transpose(2);
265
+ break;
266
+ case Direction.CW2:
267
+ case Direction.CCW2:
268
+ // 180° rotation (same for both directions): hflip + vflip
269
+ const hflipRes = (0, ffmpeg_1.makeHflip)([this.looseEnd]);
270
+ this.looseEnd = hflipRes.outputs[0];
271
+ this.buf.append(hflipRes);
272
+ const vflipRes = (0, ffmpeg_1.makeVflip)([this.looseEnd]);
273
+ this.looseEnd = vflipRes.outputs[0];
274
+ this.buf.append(vflipRes);
275
+ break;
276
+ }
277
+ return this;
278
+ }
279
+ concatStream(stream) {
280
+ return this.concatStreams([stream]);
281
+ }
282
+ concatStreams(streams) {
283
+ // todo: check streams type here, it can either be all audio or all video
284
+ const res = (0, ffmpeg_1.makeConcat)([
285
+ this.looseEnd,
286
+ ...streams.map((st) => st.getLooseEnd()),
287
+ ]);
288
+ this.looseEnd = res.outputs[0];
289
+ if (res.outputs.length > 1) {
290
+ throw new Error('concat produced several outputs, possible mixup between video and audio streams');
291
+ }
292
+ this.buf.append(res);
293
+ return this;
294
+ }
295
+ mixStream(stream, options) {
296
+ return this.mixStreams([stream], options);
297
+ }
298
+ mixStreams(streams, options) {
299
+ const res = (0, ffmpeg_1.makeAmix)([this.looseEnd, ...streams.map((st) => st.getLooseEnd())], options);
300
+ this.looseEnd = res.outputs[0];
301
+ this.buf.append(res);
302
+ return this;
303
+ }
304
+ tPad(options = {}) {
305
+ const res = (0, ffmpeg_1.makeTPad)([this.looseEnd], options);
306
+ this.looseEnd = res.outputs[0];
307
+ this.buf.append(res);
308
+ return this;
309
+ }
310
+ /*
311
+ this stream becomes the bottom layer, and the joining stream - top layer
312
+ For video: uses overlay filter
313
+ For audio: uses amix filter
314
+ */
315
+ overlayStream(stream, options) {
316
+ const offset = options.offset;
317
+ const flip = !!options.flipLayers;
318
+ const isAudio = this.looseEnd.isAudio;
319
+ // Validate that both streams are of the same type
320
+ if (isAudio !== stream.getLooseEnd().isAudio) {
321
+ throw new Error('overlayStream: both streams must be of the same type (both video or both audio)');
322
+ }
323
+ if (!offset || !offset.otherStreamOffsetLeft) {
324
+ // usual overlay/mix, no offset
325
+ if (isAudio) {
326
+ const res = (0, ffmpeg_1.makeAmix)([this.looseEnd, stream.getLooseEnd()]);
327
+ this.looseEnd = res.outputs[0];
328
+ this.buf.append(res);
329
+ }
330
+ else {
331
+ const res = (0, ffmpeg_1.makeOverlay)(flip
332
+ ? [stream.getLooseEnd(), this.looseEnd]
333
+ : [this.looseEnd, stream.getLooseEnd()]);
334
+ this.looseEnd = res.outputs[0];
335
+ this.buf.append(res);
336
+ }
337
+ }
338
+ else {
339
+ if (offset.streamDuration === undefined) {
340
+ throw new Error('exact duration of the fragment in the stream must be provided');
341
+ }
342
+ if (offset.otherStreamDuration === undefined) {
343
+ throw new Error('exact duration of the fragment in the joining stream must be provided');
344
+ }
345
+ const offsetLeft = offset.otherStreamOffsetLeft;
346
+ if (offsetLeft > 0) {
347
+ // Pad the joining stream on the left
348
+ stream.tPad({
349
+ start: offsetLeft,
350
+ ...(isAudio ? {} : { color: Colors.Transparent }),
351
+ });
352
+ // Pad the main stream on the right if needed
353
+ const mainLeftover = offset.otherStreamDuration + offsetLeft - offset.streamDuration;
354
+ if (mainLeftover > 0) {
355
+ this.tPad({
356
+ stop: mainLeftover,
357
+ ...(isAudio ? {} : { color: Colors.Transparent }),
358
+ });
359
+ }
360
+ // Mix or overlay the streams
361
+ if (isAudio) {
362
+ const res = (0, ffmpeg_1.makeAmix)([this.looseEnd, stream.getLooseEnd()]);
363
+ this.looseEnd = res.outputs[0];
364
+ this.buf.append(res);
365
+ }
366
+ else {
367
+ const overlayRes = (0, ffmpeg_1.makeOverlay)(flip
368
+ ? [stream.getLooseEnd(), this.looseEnd]
369
+ : [this.looseEnd, stream.getLooseEnd()]);
370
+ this.looseEnd = overlayRes.outputs[0];
371
+ this.buf.append(overlayRes);
372
+ }
373
+ }
374
+ else if (offsetLeft < 0) {
375
+ throw new Error('negative offset is not supported for overlayStream');
376
+ }
377
+ }
378
+ return this;
379
+ }
380
+ endTo(label) {
381
+ const res = (0, ffmpeg_1.makeNull)([this.looseEnd]);
382
+ res.outputs[0] = label;
383
+ this.buf.append(res);
384
+ return this;
385
+ }
386
+ /**
387
+ * Applies an Instagram-style filter to the video stream
388
+ * @param filterName - The filter to apply
389
+ */
390
+ filter(filterName) {
391
+ if (this.looseEnd.isAudio) {
392
+ throw new Error('filter() can only be applied to video streams');
393
+ }
394
+ switch (filterName) {
395
+ case VisualFilter.InstagramClarendon:
396
+ // Brightens, increases contrast and saturation
397
+ const clarendonEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
398
+ contrast: 1.2,
399
+ brightness: 0.1,
400
+ saturation: 1.3,
401
+ });
402
+ this.looseEnd = clarendonEq.outputs[0];
403
+ this.buf.append(clarendonEq);
404
+ break;
405
+ case VisualFilter.InstagramGingham:
406
+ // Vintage washed-out look
407
+ const ginghamEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
408
+ saturation: 0.6,
409
+ brightness: 0.05,
410
+ });
411
+ this.looseEnd = ginghamEq.outputs[0];
412
+ this.buf.append(ginghamEq);
413
+ const ginghamBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
414
+ rm: 0.1,
415
+ bm: 0.05,
416
+ });
417
+ this.looseEnd = ginghamBalance.outputs[0];
418
+ this.buf.append(ginghamBalance);
419
+ break;
420
+ case VisualFilter.InstagramJuno:
421
+ // High contrast, saturated, cool tones
422
+ const junoEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
423
+ contrast: 1.3,
424
+ saturation: 1.4,
425
+ });
426
+ this.looseEnd = junoEq.outputs[0];
427
+ this.buf.append(junoEq);
428
+ const junoBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
429
+ bh: 0.15,
430
+ gh: 0.1,
431
+ });
432
+ this.looseEnd = junoBalance.outputs[0];
433
+ this.buf.append(junoBalance);
434
+ break;
435
+ case VisualFilter.InstagramLark:
436
+ // Brightens, desaturated, cool tones
437
+ const larkEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
438
+ brightness: 0.15,
439
+ saturation: 0.7,
440
+ });
441
+ this.looseEnd = larkEq.outputs[0];
442
+ this.buf.append(larkEq);
443
+ const larkBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
444
+ bm: 0.1,
445
+ });
446
+ this.looseEnd = larkBalance.outputs[0];
447
+ this.buf.append(larkBalance);
448
+ break;
449
+ case VisualFilter.InstagramLudwig:
450
+ // Cool tones, subtle vignette
451
+ const ludwigBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
452
+ bm: 0.08,
453
+ bs: 0.05,
454
+ });
455
+ this.looseEnd = ludwigBalance.outputs[0];
456
+ this.buf.append(ludwigBalance);
457
+ const ludwigVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
458
+ angle: 'PI/4',
459
+ });
460
+ this.looseEnd = ludwigVignette.outputs[0];
461
+ this.buf.append(ludwigVignette);
462
+ break;
463
+ case VisualFilter.InstagramNashville:
464
+ // Warm vintage, pink tint, vignette
465
+ const nashvilleBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
466
+ rm: 0.2,
467
+ rh: 0.1,
468
+ bm: -0.1,
469
+ });
470
+ this.looseEnd = nashvilleBalance.outputs[0];
471
+ this.buf.append(nashvilleBalance);
472
+ const nashvilleEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
473
+ contrast: 0.9,
474
+ saturation: 1.2,
475
+ });
476
+ this.looseEnd = nashvilleEq.outputs[0];
477
+ this.buf.append(nashvilleEq);
478
+ const nashvilleVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
479
+ angle: 'PI/4.5',
480
+ });
481
+ this.looseEnd = nashvilleVignette.outputs[0];
482
+ this.buf.append(nashvilleVignette);
483
+ break;
484
+ case VisualFilter.InstagramValencia:
485
+ // Warm tones, slight fade
486
+ const valenciaBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
487
+ rm: 0.15,
488
+ gm: 0.05,
489
+ });
490
+ this.looseEnd = valenciaBalance.outputs[0];
491
+ this.buf.append(valenciaBalance);
492
+ const valenciaEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
493
+ contrast: 0.95,
494
+ brightness: 0.05,
495
+ });
496
+ this.looseEnd = valenciaEq.outputs[0];
497
+ this.buf.append(valenciaEq);
498
+ break;
499
+ case VisualFilter.InstagramXProII:
500
+ // High contrast, warm highlights, cool shadows, vignette
501
+ const xproBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
502
+ rh: 0.2,
503
+ bs: 0.15,
504
+ });
505
+ this.looseEnd = xproBalance.outputs[0];
506
+ this.buf.append(xproBalance);
507
+ const xproEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
508
+ contrast: 1.4,
509
+ saturation: 1.2,
510
+ });
511
+ this.looseEnd = xproEq.outputs[0];
512
+ this.buf.append(xproEq);
513
+ const xproVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
514
+ angle: 'PI/4',
515
+ });
516
+ this.looseEnd = xproVignette.outputs[0];
517
+ this.buf.append(xproVignette);
518
+ break;
519
+ case VisualFilter.InstagramWillow:
520
+ // Black and white-ish, desaturated, slight yellow tint
521
+ const willowEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
522
+ saturation: 0.2,
523
+ brightness: 0.05,
524
+ });
525
+ this.looseEnd = willowEq.outputs[0];
526
+ this.buf.append(willowEq);
527
+ const willowBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
528
+ rm: 0.05,
529
+ gm: 0.05,
530
+ });
531
+ this.looseEnd = willowBalance.outputs[0];
532
+ this.buf.append(willowBalance);
533
+ break;
534
+ case VisualFilter.InstagramLoFi:
535
+ // High contrast, high saturation, vignette
536
+ const lofiEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
537
+ contrast: 1.5,
538
+ saturation: 1.4,
539
+ });
540
+ this.looseEnd = lofiEq.outputs[0];
541
+ this.buf.append(lofiEq);
542
+ const lofiVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
543
+ angle: 'PI/4',
544
+ });
545
+ this.looseEnd = lofiVignette.outputs[0];
546
+ this.buf.append(lofiVignette);
547
+ break;
548
+ case VisualFilter.InstagramInkwell:
549
+ // Classic black and white
550
+ const inkwellEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
551
+ saturation: 0,
552
+ contrast: 1.1,
553
+ });
554
+ this.looseEnd = inkwellEq.outputs[0];
555
+ this.buf.append(inkwellEq);
556
+ break;
557
+ case VisualFilter.InstagramMoon:
558
+ // Black and white with high contrast and cool tone
559
+ const moonEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
560
+ saturation: 0,
561
+ contrast: 1.4,
562
+ brightness: -0.05,
563
+ });
564
+ this.looseEnd = moonEq.outputs[0];
565
+ this.buf.append(moonEq);
566
+ const moonBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
567
+ bs: 0.1,
568
+ bm: 0.08,
569
+ });
570
+ this.looseEnd = moonBalance.outputs[0];
571
+ this.buf.append(moonBalance);
572
+ const moonVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
573
+ angle: 'PI/4.5',
574
+ });
575
+ this.looseEnd = moonVignette.outputs[0];
576
+ this.buf.append(moonVignette);
577
+ break;
578
+ case VisualFilter.InstagramHudson:
579
+ // Cool tones, high contrast, vignette
580
+ const hudsonBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
581
+ bm: 0.2,
582
+ bs: 0.15,
583
+ });
584
+ this.looseEnd = hudsonBalance.outputs[0];
585
+ this.buf.append(hudsonBalance);
586
+ const hudsonEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
587
+ contrast: 1.3,
588
+ });
589
+ this.looseEnd = hudsonEq.outputs[0];
590
+ this.buf.append(hudsonEq);
591
+ const hudsonVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
592
+ angle: 'PI/4.5',
593
+ });
594
+ this.looseEnd = hudsonVignette.outputs[0];
595
+ this.buf.append(hudsonVignette);
596
+ break;
597
+ case VisualFilter.InstagramToaster:
598
+ // Warm tones, vignette
599
+ const toasterBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
600
+ rm: 0.25,
601
+ rh: 0.15,
602
+ });
603
+ this.looseEnd = toasterBalance.outputs[0];
604
+ this.buf.append(toasterBalance);
605
+ const toasterEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
606
+ contrast: 1.2,
607
+ });
608
+ this.looseEnd = toasterEq.outputs[0];
609
+ this.buf.append(toasterEq);
610
+ const toasterVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
611
+ angle: 'PI/4',
612
+ });
613
+ this.looseEnd = toasterVignette.outputs[0];
614
+ this.buf.append(toasterVignette);
615
+ break;
616
+ case VisualFilter.InstagramWalden:
617
+ // Increased exposure, yellow tones
618
+ const waldenBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
619
+ rm: 0.1,
620
+ gm: 0.1,
621
+ });
622
+ this.looseEnd = waldenBalance.outputs[0];
623
+ this.buf.append(waldenBalance);
624
+ const waldenEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
625
+ brightness: 0.15,
626
+ saturation: 1.1,
627
+ });
628
+ this.looseEnd = waldenEq.outputs[0];
629
+ this.buf.append(waldenEq);
630
+ break;
631
+ case VisualFilter.InstagramRise:
632
+ // Soft, warm glow
633
+ const riseBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
634
+ rm: 0.12,
635
+ rh: 0.08,
636
+ });
637
+ this.looseEnd = riseBalance.outputs[0];
638
+ this.buf.append(riseBalance);
639
+ const riseEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
640
+ brightness: 0.1,
641
+ contrast: 0.9,
642
+ saturation: 1.15,
643
+ });
644
+ this.looseEnd = riseEq.outputs[0];
645
+ this.buf.append(riseEq);
646
+ break;
647
+ case VisualFilter.InstagramAmaro:
648
+ // Increases contrast, adds vignette, cool tone
649
+ const amaroBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
650
+ bm: 0.1,
651
+ });
652
+ this.looseEnd = amaroBalance.outputs[0];
653
+ this.buf.append(amaroBalance);
654
+ const amaroEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
655
+ contrast: 1.3,
656
+ saturation: 1.2,
657
+ });
658
+ this.looseEnd = amaroEq.outputs[0];
659
+ this.buf.append(amaroEq);
660
+ const amaroVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
661
+ angle: 'PI/4.5',
662
+ });
663
+ this.looseEnd = amaroVignette.outputs[0];
664
+ this.buf.append(amaroVignette);
665
+ break;
666
+ case VisualFilter.InstagramMayfair:
667
+ // Warm center, cool edges, vignette
668
+ const mayfairBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
669
+ rh: 0.15,
670
+ bs: 0.1,
671
+ });
672
+ this.looseEnd = mayfairBalance.outputs[0];
673
+ this.buf.append(mayfairBalance);
674
+ const mayfairEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
675
+ contrast: 1.1,
676
+ saturation: 1.15,
677
+ });
678
+ this.looseEnd = mayfairEq.outputs[0];
679
+ this.buf.append(mayfairEq);
680
+ const mayfairVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
681
+ angle: 'PI/4',
682
+ });
683
+ this.looseEnd = mayfairVignette.outputs[0];
684
+ this.buf.append(mayfairVignette);
685
+ break;
686
+ case VisualFilter.InstagramEarlybird:
687
+ // Vintage sepia, vignette
688
+ const earlybirdBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
689
+ rm: 0.2,
690
+ gm: 0.1,
691
+ bm: -0.15,
692
+ });
693
+ this.looseEnd = earlybirdBalance.outputs[0];
694
+ this.buf.append(earlybirdBalance);
695
+ const earlybirdEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
696
+ contrast: 1.2,
697
+ saturation: 1.1,
698
+ });
699
+ this.looseEnd = earlybirdEq.outputs[0];
700
+ this.buf.append(earlybirdEq);
701
+ const earlybirdVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
702
+ angle: 'PI/4',
703
+ });
704
+ this.looseEnd = earlybirdVignette.outputs[0];
705
+ this.buf.append(earlybirdVignette);
706
+ break;
707
+ case VisualFilter.InstagramSutro:
708
+ // Muted colors, purple/brown tint, vignette
709
+ const sutroBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
710
+ rm: 0.1,
711
+ bm: 0.15,
712
+ });
713
+ this.looseEnd = sutroBalance.outputs[0];
714
+ this.buf.append(sutroBalance);
715
+ const sutroEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
716
+ saturation: 0.8,
717
+ contrast: 1.2,
718
+ });
719
+ this.looseEnd = sutroEq.outputs[0];
720
+ this.buf.append(sutroEq);
721
+ const sutroVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
722
+ angle: 'PI/3.5',
723
+ });
724
+ this.looseEnd = sutroVignette.outputs[0];
725
+ this.buf.append(sutroVignette);
726
+ break;
727
+ case VisualFilter.InstagramAden:
728
+ // Muted, cool tones, slight vignette
729
+ const adenBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
730
+ bm: 0.12,
731
+ });
732
+ this.looseEnd = adenBalance.outputs[0];
733
+ this.buf.append(adenBalance);
734
+ const adenEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
735
+ saturation: 0.85,
736
+ brightness: 0.08,
737
+ });
738
+ this.looseEnd = adenEq.outputs[0];
739
+ this.buf.append(adenEq);
740
+ const adenVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
741
+ angle: 'PI/5',
742
+ });
743
+ this.looseEnd = adenVignette.outputs[0];
744
+ this.buf.append(adenVignette);
745
+ break;
746
+ case VisualFilter.InstagramCrema:
747
+ // Creamy warmth, slight vignette
748
+ const cremaBalance = (0, ffmpeg_1.makeColorBalance)([this.looseEnd], {
749
+ rm: 0.08,
750
+ gm: 0.05,
751
+ });
752
+ this.looseEnd = cremaBalance.outputs[0];
753
+ this.buf.append(cremaBalance);
754
+ const cremaEq = (0, ffmpeg_1.makeEq)([this.looseEnd], {
755
+ brightness: 0.05,
756
+ contrast: 0.95,
757
+ });
758
+ this.looseEnd = cremaEq.outputs[0];
759
+ this.buf.append(cremaEq);
760
+ const cremaVignette = (0, ffmpeg_1.makeVignette)([this.looseEnd], {
761
+ angle: 'PI/5',
762
+ });
763
+ this.looseEnd = cremaVignette.outputs[0];
764
+ this.buf.append(cremaVignette);
765
+ break;
766
+ default:
767
+ throw new Error(`Unknown Instagram filter: ${filterName}`);
768
+ }
769
+ return this;
770
+ }
771
+ getLooseEnd() {
772
+ return this.looseEnd;
773
+ }
774
+ render() {
775
+ return this.buf.render();
776
+ }
777
+ }
778
+ exports.Stream = Stream;
779
+ //# sourceMappingURL=stream.js.map