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