@gannochenko/staticstripes 0.0.23 → 0.0.24

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.
@@ -36,6 +36,16 @@ export const getSampleSequences = (
36
36
  objectFitContainAmbientBrightness: -0.1,
37
37
  objectFitContainAmbientSaturation: 0.7,
38
38
  objectFitContainPillarboxColor: '#000000',
39
+ objectFitKenBurns: 'zoom-in' as const,
40
+ objectFitKenBurnsZoom: 30,
41
+ objectFitKenBurnsEffectDuration: 0,
42
+ objectFitKenBurnsEasing: 'linear' as const,
43
+ objectFitKenBurnsFocalX: 50,
44
+ objectFitKenBurnsFocalY: 50,
45
+ objectFitKenBurnsPanStartX: 0,
46
+ objectFitKenBurnsPanStartY: 0,
47
+ objectFitKenBurnsPanEndX: 100,
48
+ objectFitKenBurnsPanEndY: 100,
39
49
  chromakey: false,
40
50
  chromakeyBlend: 0.1,
41
51
  sound: 'on' as const,
@@ -60,6 +70,16 @@ export const getSampleSequences = (
60
70
  objectFitContainAmbientBrightness: -0.1,
61
71
  objectFitContainAmbientSaturation: 0.7,
62
72
  objectFitContainPillarboxColor: '#000000',
73
+ objectFitKenBurns: 'zoom-in' as const,
74
+ objectFitKenBurnsZoom: 30,
75
+ objectFitKenBurnsEffectDuration: 0,
76
+ objectFitKenBurnsEasing: 'linear' as const,
77
+ objectFitKenBurnsFocalX: 50,
78
+ objectFitKenBurnsFocalY: 50,
79
+ objectFitKenBurnsPanStartX: 0,
80
+ objectFitKenBurnsPanStartY: 0,
81
+ objectFitKenBurnsPanEndX: 100,
82
+ objectFitKenBurnsPanEndY: 100,
63
83
  chromakey: false,
64
84
  chromakeyBlend: 0.1,
65
85
  sound: 'on' as const,
@@ -84,6 +104,16 @@ export const getSampleSequences = (
84
104
  objectFitContainAmbientBrightness: -0.1,
85
105
  objectFitContainAmbientSaturation: 0.7,
86
106
  objectFitContainPillarboxColor: '#000000',
107
+ objectFitKenBurns: 'zoom-in' as const,
108
+ objectFitKenBurnsZoom: 30,
109
+ objectFitKenBurnsEffectDuration: 0,
110
+ objectFitKenBurnsEasing: 'linear' as const,
111
+ objectFitKenBurnsFocalX: 50,
112
+ objectFitKenBurnsFocalY: 50,
113
+ objectFitKenBurnsPanStartX: 0,
114
+ objectFitKenBurnsPanStartY: 0,
115
+ objectFitKenBurnsPanEndX: 100,
116
+ objectFitKenBurnsPanEndY: 100,
87
117
  chromakey: true,
88
118
  chromakeyBlend: 0.1,
89
119
  chromakeySimilarity: 0.1,
@@ -108,6 +138,16 @@ export const getSampleSequences = (
108
138
  objectFitContainAmbientBrightness: -0.1,
109
139
  objectFitContainAmbientSaturation: 0.7,
110
140
  objectFitContainPillarboxColor: '#000000',
141
+ objectFitKenBurns: 'zoom-in' as const,
142
+ objectFitKenBurnsZoom: 30,
143
+ objectFitKenBurnsEffectDuration: 0,
144
+ objectFitKenBurnsEasing: 'linear' as const,
145
+ objectFitKenBurnsFocalX: 50,
146
+ objectFitKenBurnsFocalY: 50,
147
+ objectFitKenBurnsPanStartX: 0,
148
+ objectFitKenBurnsPanStartY: 0,
149
+ objectFitKenBurnsPanEndX: 100,
150
+ objectFitKenBurnsPanEndY: 100,
111
151
  chromakey: false,
112
152
  chromakeyBlend: 0.1,
113
153
  sound: 'on' as const,
@@ -132,6 +172,16 @@ export const getSampleSequences = (
132
172
  objectFitContainAmbientBrightness: -0.1,
133
173
  objectFitContainAmbientSaturation: 0.7,
134
174
  objectFitContainPillarboxColor: '#000000',
175
+ objectFitKenBurns: 'zoom-in' as const,
176
+ objectFitKenBurnsZoom: 30,
177
+ objectFitKenBurnsEffectDuration: 0,
178
+ objectFitKenBurnsEasing: 'linear' as const,
179
+ objectFitKenBurnsFocalX: 50,
180
+ objectFitKenBurnsFocalY: 50,
181
+ objectFitKenBurnsPanStartX: 0,
182
+ objectFitKenBurnsPanStartY: 0,
183
+ objectFitKenBurnsPanEndX: 100,
184
+ objectFitKenBurnsPanEndY: 100,
135
185
  chromakey: false,
136
186
  chromakeyBlend: 0.1,
137
187
  sound: 'on' as const,
@@ -168,6 +218,16 @@ export const getSampleSequences = (
168
218
  objectFitContainAmbientBrightness: -0.1,
169
219
  objectFitContainAmbientSaturation: 0.7,
170
220
  objectFitContainPillarboxColor: '#000000',
221
+ objectFitKenBurns: 'zoom-in' as const,
222
+ objectFitKenBurnsZoom: 30,
223
+ objectFitKenBurnsEffectDuration: 0,
224
+ objectFitKenBurnsEasing: 'linear' as const,
225
+ objectFitKenBurnsFocalX: 50,
226
+ objectFitKenBurnsFocalY: 50,
227
+ objectFitKenBurnsPanStartX: 0,
228
+ objectFitKenBurnsPanStartY: 0,
229
+ objectFitKenBurnsPanEndX: 100,
230
+ objectFitKenBurnsPanEndY: 100,
171
231
  chromakey: false,
172
232
  chromakeyBlend: 0.1,
173
233
  sound: 'on' as const,
@@ -204,6 +264,16 @@ export const getSampleSequences = (
204
264
  objectFitContainAmbientBrightness: -0.1,
205
265
  objectFitContainAmbientSaturation: 0.7,
206
266
  objectFitContainPillarboxColor: '#000000',
267
+ objectFitKenBurns: 'zoom-in' as const,
268
+ objectFitKenBurnsZoom: 30,
269
+ objectFitKenBurnsEffectDuration: 0,
270
+ objectFitKenBurnsEasing: 'linear' as const,
271
+ objectFitKenBurnsFocalX: 50,
272
+ objectFitKenBurnsFocalY: 50,
273
+ objectFitKenBurnsPanStartX: 0,
274
+ objectFitKenBurnsPanStartY: 0,
275
+ objectFitKenBurnsPanEndX: 100,
276
+ objectFitKenBurnsPanEndY: 100,
207
277
  chromakey: false,
208
278
  chromakeyBlend: 0.1,
209
279
  sound: 'on' as const,
@@ -218,6 +288,229 @@ export const getSampleSequences = (
218
288
  );
219
289
  seq3.build();
220
290
 
291
+ // Ken Burns effect test sequence - using intro_image for all effect types
292
+ const seq4 = new Sequence(
293
+ buf,
294
+ {
295
+ fragments: [
296
+ // zoom-in effect with custom focal point
297
+ {
298
+ id: 'kb_zoom_in',
299
+ enabled: true,
300
+ assetName: 'intro_image',
301
+ duration: 3000,
302
+ trimLeft: 0,
303
+ overlayLeft: 0,
304
+ overlayZIndex: 1,
305
+ transitionIn: '',
306
+ transitionInDuration: 0,
307
+ transitionOut: '',
308
+ transitionOutDuration: 0,
309
+ objectFit: 'ken-burns',
310
+ objectFitContain: 'ambient',
311
+ objectFitContainAmbientBlurStrength: 25,
312
+ objectFitContainAmbientBrightness: -0.1,
313
+ objectFitContainAmbientSaturation: 0.7,
314
+ objectFitContainPillarboxColor: '#000000',
315
+ objectFitKenBurns: 'zoom-in' as const,
316
+ objectFitKenBurnsZoom: 50,
317
+ objectFitKenBurnsEffectDuration: 1000,
318
+ objectFitKenBurnsEasing: 'ease-in-out' as const,
319
+ objectFitKenBurnsFocalX: 30,
320
+ objectFitKenBurnsFocalY: 40,
321
+ objectFitKenBurnsPanStartX: 0,
322
+ objectFitKenBurnsPanStartY: 0,
323
+ objectFitKenBurnsPanEndX: 100,
324
+ objectFitKenBurnsPanEndY: 100,
325
+ chromakey: false,
326
+ chromakeyBlend: 0.1,
327
+ sound: 'on' as const,
328
+ chromakeySimilarity: 0.1,
329
+ chromakeyColor: '#000000',
330
+ },
331
+ // zoom-out effect with center focal point
332
+ {
333
+ id: 'kb_zoom_out',
334
+ enabled: true,
335
+ assetName: 'intro_image',
336
+ duration: 3000,
337
+ trimLeft: 0,
338
+ overlayLeft: 0,
339
+ overlayZIndex: 1,
340
+ transitionIn: '',
341
+ transitionInDuration: 0,
342
+ transitionOut: '',
343
+ transitionOutDuration: 0,
344
+ objectFit: 'ken-burns',
345
+ objectFitContain: 'ambient',
346
+ objectFitContainAmbientBlurStrength: 25,
347
+ objectFitContainAmbientBrightness: -0.1,
348
+ objectFitContainAmbientSaturation: 0.7,
349
+ objectFitContainPillarboxColor: '#000000',
350
+ objectFitKenBurns: 'zoom-out' as const,
351
+ objectFitKenBurnsZoom: 40,
352
+ objectFitKenBurnsEffectDuration: 2000,
353
+ objectFitKenBurnsEasing: 'ease-in' as const,
354
+ objectFitKenBurnsFocalX: 50,
355
+ objectFitKenBurnsFocalY: 50,
356
+ objectFitKenBurnsPanStartX: 0,
357
+ objectFitKenBurnsPanStartY: 0,
358
+ objectFitKenBurnsPanEndX: 100,
359
+ objectFitKenBurnsPanEndY: 100,
360
+ chromakey: false,
361
+ chromakeyBlend: 0.1,
362
+ sound: 'on' as const,
363
+ chromakeySimilarity: 0.1,
364
+ chromakeyColor: '#000000',
365
+ },
366
+ // pan-left effect
367
+ {
368
+ id: 'kb_pan_left',
369
+ enabled: true,
370
+ assetName: 'intro_image',
371
+ duration: 3000,
372
+ trimLeft: 0,
373
+ overlayLeft: 0,
374
+ overlayZIndex: 1,
375
+ transitionIn: '',
376
+ transitionInDuration: 0,
377
+ transitionOut: '',
378
+ transitionOutDuration: 0,
379
+ objectFit: 'ken-burns',
380
+ objectFitContain: 'ambient',
381
+ objectFitContainAmbientBlurStrength: 25,
382
+ objectFitContainAmbientBrightness: -0.1,
383
+ objectFitContainAmbientSaturation: 0.7,
384
+ objectFitContainPillarboxColor: '#000000',
385
+ objectFitKenBurns: 'pan-left' as const,
386
+ objectFitKenBurnsZoom: 30,
387
+ objectFitKenBurnsEffectDuration: 1500,
388
+ objectFitKenBurnsEasing: 'linear' as const,
389
+ objectFitKenBurnsFocalX: 50,
390
+ objectFitKenBurnsFocalY: 50,
391
+ objectFitKenBurnsPanStartX: 0,
392
+ objectFitKenBurnsPanStartY: 0,
393
+ objectFitKenBurnsPanEndX: 100,
394
+ objectFitKenBurnsPanEndY: 100,
395
+ chromakey: false,
396
+ chromakeyBlend: 0.1,
397
+ sound: 'on' as const,
398
+ chromakeySimilarity: 0.1,
399
+ chromakeyColor: '#000000',
400
+ },
401
+ // pan-right effect
402
+ {
403
+ id: 'kb_pan_right',
404
+ enabled: true,
405
+ assetName: 'intro_image',
406
+ duration: 3000,
407
+ trimLeft: 0,
408
+ overlayLeft: 0,
409
+ overlayZIndex: 1,
410
+ transitionIn: '',
411
+ transitionInDuration: 0,
412
+ transitionOut: '',
413
+ transitionOutDuration: 0,
414
+ objectFit: 'ken-burns',
415
+ objectFitContain: 'ambient',
416
+ objectFitContainAmbientBlurStrength: 25,
417
+ objectFitContainAmbientBrightness: -0.1,
418
+ objectFitContainAmbientSaturation: 0.7,
419
+ objectFitContainPillarboxColor: '#000000',
420
+ objectFitKenBurns: 'pan-right' as const,
421
+ objectFitKenBurnsZoom: 50,
422
+ objectFitKenBurnsEffectDuration: 1500,
423
+ objectFitKenBurnsEasing: 'ease-out' as const,
424
+ objectFitKenBurnsFocalX: 50,
425
+ objectFitKenBurnsFocalY: 50,
426
+ objectFitKenBurnsPanStartX: 0,
427
+ objectFitKenBurnsPanStartY: 0,
428
+ objectFitKenBurnsPanEndX: 100,
429
+ objectFitKenBurnsPanEndY: 100,
430
+ chromakey: false,
431
+ chromakeyBlend: 0.1,
432
+ sound: 'on' as const,
433
+ chromakeySimilarity: 0.1,
434
+ chromakeyColor: '#000000',
435
+ },
436
+ // pan-top effect
437
+ {
438
+ id: 'kb_pan_top',
439
+ enabled: true,
440
+ assetName: 'intro_image',
441
+ duration: 3000,
442
+ trimLeft: 0,
443
+ overlayLeft: 0,
444
+ overlayZIndex: 1,
445
+ transitionIn: '',
446
+ transitionInDuration: 0,
447
+ transitionOut: '',
448
+ transitionOutDuration: 0,
449
+ objectFit: 'ken-burns',
450
+ objectFitContain: 'ambient',
451
+ objectFitContainAmbientBlurStrength: 25,
452
+ objectFitContainAmbientBrightness: -0.1,
453
+ objectFitContainAmbientSaturation: 0.7,
454
+ objectFitContainPillarboxColor: '#000000',
455
+ objectFitKenBurns: 'pan-top' as const,
456
+ objectFitKenBurnsZoom: 30,
457
+ objectFitKenBurnsEffectDuration: 1500,
458
+ objectFitKenBurnsEasing: 'ease-in' as const,
459
+ objectFitKenBurnsFocalX: 50,
460
+ objectFitKenBurnsFocalY: 50,
461
+ objectFitKenBurnsPanStartX: 0,
462
+ objectFitKenBurnsPanStartY: 0,
463
+ objectFitKenBurnsPanEndX: 100,
464
+ objectFitKenBurnsPanEndY: 100,
465
+ chromakey: false,
466
+ chromakeyBlend: 0.1,
467
+ sound: 'on' as const,
468
+ chromakeySimilarity: 0.1,
469
+ chromakeyColor: '#000000',
470
+ },
471
+ // pan-bottom effect
472
+ {
473
+ id: 'kb_pan_bottom',
474
+ enabled: true,
475
+ assetName: 'intro_image',
476
+ duration: 3000,
477
+ trimLeft: 0,
478
+ overlayLeft: 0,
479
+ overlayZIndex: 1,
480
+ transitionIn: '',
481
+ transitionInDuration: 0,
482
+ transitionOut: '',
483
+ transitionOutDuration: 0,
484
+ objectFit: 'ken-burns',
485
+ objectFitContain: 'ambient',
486
+ objectFitContainAmbientBlurStrength: 25,
487
+ objectFitContainAmbientBrightness: -0.1,
488
+ objectFitContainAmbientSaturation: 0.7,
489
+ objectFitContainPillarboxColor: '#000000',
490
+ objectFitKenBurns: 'pan-bottom' as const,
491
+ objectFitKenBurnsZoom: 40,
492
+ objectFitKenBurnsEffectDuration: 1500,
493
+ objectFitKenBurnsEasing: 'ease-in-out' as const,
494
+ objectFitKenBurnsFocalX: 50,
495
+ objectFitKenBurnsFocalY: 50,
496
+ objectFitKenBurnsPanStartX: 0,
497
+ objectFitKenBurnsPanStartY: 0,
498
+ objectFitKenBurnsPanEndX: 100,
499
+ objectFitKenBurnsPanEndY: 100,
500
+ chromakey: false,
501
+ chromakeyBlend: 0.1,
502
+ sound: 'on' as const,
503
+ chromakeySimilarity: 0.1,
504
+ chromakeyColor: '#000000',
505
+ },
506
+ ],
507
+ },
508
+ output,
509
+ project.getAssetManager(),
510
+ expressionContext,
511
+ );
512
+ seq4.build();
513
+
221
514
  seq1.overlayWith(seq2);
222
515
  seq1.overlayWith(seq3);
223
516
 
package/src/sequence.ts CHANGED
@@ -122,6 +122,12 @@ export class Sequence {
122
122
  }
123
123
  }
124
124
 
125
+ // Convert deprecated JPEG pixel format (yuvj420p) to standard yuv420p early
126
+ // This prevents swscaler warnings from appearing in all subsequent filters
127
+ if (asset.hasVideo && asset.type === 'image') {
128
+ currentVideoStream.convertPixelFormat('yuv420p');
129
+ }
130
+
125
131
  // Apply visual filter early for static images (before padding/cloning)
126
132
  // This is more efficient as ffmpeg processes the filter once, then clones the filtered frame
127
133
  if (
@@ -135,9 +141,11 @@ export class Sequence {
135
141
  if (
136
142
  asset.duration === 0 &&
137
143
  calculatedDuration > 0 &&
138
- asset.type === 'image'
144
+ asset.type === 'image' &&
145
+ fragment.objectFit !== 'ken-burns'
139
146
  ) {
140
147
  // special case for images - extend static image to desired duration
148
+ // Skip tpad for Ken Burns - zoompan will generate the frames
141
149
  currentVideoStream.tPad({
142
150
  start: calculatedDuration,
143
151
  startMode: 'clone',
@@ -150,7 +158,25 @@ export class Sequence {
150
158
  currentVideoStream.fps(this.output.fps);
151
159
 
152
160
  // fitting the video stream into the output frame
153
- if (fragment.objectFit === 'cover') {
161
+ if (fragment.objectFit === 'ken-burns') {
162
+ // Ken Burns effect (zoom/pan)
163
+ currentVideoStream.kenBurns({
164
+ effect: fragment.objectFitKenBurns,
165
+ zoom: fragment.objectFitKenBurnsZoom,
166
+ effectDuration: fragment.objectFitKenBurnsEffectDuration,
167
+ fragmentDuration: calculatedDuration,
168
+ easing: fragment.objectFitKenBurnsEasing,
169
+ width: this.output.resolution.width,
170
+ height: this.output.resolution.height,
171
+ fps: this.output.fps,
172
+ focalX: fragment.objectFitKenBurnsFocalX,
173
+ focalY: fragment.objectFitKenBurnsFocalY,
174
+ panStartX: fragment.objectFitKenBurnsPanStartX,
175
+ panStartY: fragment.objectFitKenBurnsPanStartY,
176
+ panEndX: fragment.objectFitKenBurnsPanEndX,
177
+ panEndY: fragment.objectFitKenBurnsPanEndY,
178
+ });
179
+ } else if (fragment.objectFit === 'cover') {
154
180
  currentVideoStream.fitOutputCover(this.output.resolution);
155
181
  } else {
156
182
  const options: ObjectFitContainOptions = {};
package/src/stream.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  Millisecond,
5
5
  makeNull,
6
6
  makeFps,
7
+ makeFormat,
7
8
  makeTranspose,
8
9
  makeTrim,
9
10
  makeTPad,
@@ -17,6 +18,7 @@ import {
17
18
  makeOverlay,
18
19
  makeEq,
19
20
  makeChromakey,
21
+ makeKenBurns,
20
22
  makeConcat,
21
23
  makeFade,
22
24
  makeAmix,
@@ -311,6 +313,45 @@ export class Stream {
311
313
  return this;
312
314
  }
313
315
 
316
+ public kenBurns(parameters: {
317
+ effect: 'zoom-in' | 'zoom-out' | 'pan-left' | 'pan-right' | 'pan-top' | 'pan-bottom';
318
+ zoom: number;
319
+ effectDuration: number;
320
+ fragmentDuration: number;
321
+ easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
322
+ width: number;
323
+ height: number;
324
+ fps: number;
325
+ focalX?: number;
326
+ focalY?: number;
327
+ panStartX?: number;
328
+ panStartY?: number;
329
+ panEndX?: number;
330
+ panEndY?: number;
331
+ }): Stream {
332
+ // Apply Ken Burns effect
333
+ const kenBurnsRes = makeKenBurns([this.looseEnd], {
334
+ effect: parameters.effect,
335
+ zoom: parameters.zoom,
336
+ effectDuration: parameters.effectDuration,
337
+ fragmentDuration: parameters.fragmentDuration,
338
+ easing: parameters.easing,
339
+ width: parameters.width,
340
+ height: parameters.height,
341
+ fps: parameters.fps,
342
+ focalX: parameters.focalX,
343
+ focalY: parameters.focalY,
344
+ panStartX: parameters.panStartX,
345
+ panStartY: parameters.panStartY,
346
+ panEndX: parameters.panEndX,
347
+ panEndY: parameters.panEndY,
348
+ });
349
+ this.looseEnd = kenBurnsRes.outputs[0];
350
+ this.buf.append(kenBurnsRes);
351
+
352
+ return this;
353
+ }
354
+
314
355
  public fps(value: number): Stream {
315
356
  const res = makeFps([this.looseEnd], value);
316
357
  this.looseEnd = res.outputs[0];
@@ -320,6 +361,15 @@ export class Stream {
320
361
  return this;
321
362
  }
322
363
 
364
+ public convertPixelFormat(format: string): Stream {
365
+ const res = makeFormat([this.looseEnd], format);
366
+ this.looseEnd = res.outputs[0];
367
+
368
+ this.buf.append(res);
369
+
370
+ return this;
371
+ }
372
+
323
373
  public blur(strength: number): Stream {
324
374
  const res = makeGblur([this.looseEnd], {
325
375
  sigma: strength,
package/src/type.ts CHANGED
@@ -55,12 +55,22 @@ export type Fragment = {
55
55
  transitionInDuration: number; // how long the transition in lasts
56
56
  transitionOut: string; // how to transition out of the fragment
57
57
  transitionOutDuration: number; // how long the transition out lasts
58
- objectFit: 'cover' | 'contain';
58
+ objectFit: 'cover' | 'contain' | 'ken-burns';
59
59
  objectFitContain: 'ambient' | 'pillarbox';
60
60
  objectFitContainAmbientBlurStrength: number;
61
61
  objectFitContainAmbientBrightness: number;
62
62
  objectFitContainAmbientSaturation: number;
63
63
  objectFitContainPillarboxColor: string;
64
+ objectFitKenBurns: 'zoom-in' | 'zoom-out' | 'pan-left' | 'pan-right' | 'pan-top' | 'pan-bottom';
65
+ objectFitKenBurnsZoom: number; // zoom percentage (e.g., 30 = 30% zoom, applies to all effects)
66
+ objectFitKenBurnsEffectDuration: number; // duration of the ken burns effect in milliseconds
67
+ objectFitKenBurnsEasing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
68
+ objectFitKenBurnsFocalX: number; // focal point X in percent (0-100, for zoom effects)
69
+ objectFitKenBurnsFocalY: number; // focal point Y in percent (0-100, for zoom effects)
70
+ objectFitKenBurnsPanStartX: number; // pan start position X in percent (0-100, for horizontal pan effects)
71
+ objectFitKenBurnsPanStartY: number; // pan start position Y in percent (0-100, for vertical pan effects)
72
+ objectFitKenBurnsPanEndX: number; // pan end position X in percent (0-100, for horizontal pan effects)
73
+ objectFitKenBurnsPanEndY: number; // pan end position Y in percent (0-100, for vertical pan effects)
64
74
  chromakey: boolean;
65
75
  chromakeyBlend: number;
66
76
  chromakeySimilarity: number;