@gannochenko/staticstripes 0.0.22 → 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.
Files changed (56) hide show
  1. package/Makefile +8 -0
  2. package/dist/app-builder.d.ts +18 -0
  3. package/dist/app-builder.d.ts.map +1 -0
  4. package/dist/app-builder.js +94 -0
  5. package/dist/app-builder.js.map +1 -0
  6. package/dist/cli/commands/filters.d.ts +3 -0
  7. package/dist/cli/commands/filters.d.ts.map +1 -0
  8. package/dist/cli/commands/filters.js +21 -0
  9. package/dist/cli/commands/filters.js.map +1 -0
  10. package/dist/cli/commands/generate.d.ts.map +1 -1
  11. package/dist/cli/commands/generate.js +6 -1
  12. package/dist/cli/commands/generate.js.map +1 -1
  13. package/dist/cli/instagram/instagram-upload-strategy.d.ts +5 -0
  14. package/dist/cli/instagram/instagram-upload-strategy.d.ts.map +1 -1
  15. package/dist/cli/instagram/instagram-upload-strategy.js +46 -3
  16. package/dist/cli/instagram/instagram-upload-strategy.js.map +1 -1
  17. package/dist/cli.js +2 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/ffmpeg.d.ts +32 -0
  20. package/dist/ffmpeg.d.ts.map +1 -1
  21. package/dist/ffmpeg.js +118 -0
  22. package/dist/ffmpeg.js.map +1 -1
  23. package/dist/html-project-parser.d.ts +36 -1
  24. package/dist/html-project-parser.d.ts.map +1 -1
  25. package/dist/html-project-parser.js +332 -15
  26. package/dist/html-project-parser.js.map +1 -1
  27. package/dist/project.d.ts +4 -1
  28. package/dist/project.d.ts.map +1 -1
  29. package/dist/project.js +50 -1
  30. package/dist/project.js.map +1 -1
  31. package/dist/sample-sequences.d.ts.map +1 -1
  32. package/dist/sample-sequences.js +293 -0
  33. package/dist/sample-sequences.js.map +1 -1
  34. package/dist/sequence.d.ts +4 -1
  35. package/dist/sequence.d.ts.map +1 -1
  36. package/dist/sequence.js +71 -21
  37. package/dist/sequence.js.map +1 -1
  38. package/dist/stream.d.ts +17 -0
  39. package/dist/stream.d.ts.map +1 -1
  40. package/dist/stream.js +28 -0
  41. package/dist/stream.js.map +1 -1
  42. package/dist/type.d.ts +29 -2
  43. package/dist/type.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/app-builder.ts +113 -0
  46. package/src/cli/commands/filters.ts +21 -0
  47. package/src/cli/commands/generate.ts +10 -1
  48. package/src/cli/instagram/instagram-upload-strategy.ts +61 -1
  49. package/src/cli.ts +2 -0
  50. package/src/ffmpeg.ts +161 -0
  51. package/src/html-project-parser.ts +410 -28
  52. package/src/project.ts +62 -0
  53. package/src/sample-sequences.ts +300 -0
  54. package/src/sequence.ts +78 -22
  55. package/src/stream.ts +50 -0
  56. package/src/type.ts +31 -2
package/src/ffmpeg.ts CHANGED
@@ -427,6 +427,26 @@ export function makeFps(inputs: Label[], fps: number): Filter {
427
427
  return new Filter(inputs, [output], `fps=${fps}`);
428
428
  }
429
429
 
430
+ export function makeFormat(inputs: Label[], format: string): Filter {
431
+ if (inputs.length !== 1) {
432
+ throw new Error(`makeFormat: expects one input`);
433
+ }
434
+
435
+ const input1 = inputs[0];
436
+ if (input1.isAudio) {
437
+ throw new Error(
438
+ `makeFormat: input1 must be video, got audio (tag: ${input1.tag})`,
439
+ );
440
+ }
441
+
442
+ const output = {
443
+ tag: getLabel(),
444
+ isAudio: false,
445
+ };
446
+
447
+ return new Filter(inputs, [output], `format=${format}`);
448
+ }
449
+
430
450
  export function makeScale(
431
451
  inputs: Label[],
432
452
  options: { width: number | string; height: number | string; flags?: string },
@@ -1183,6 +1203,147 @@ export function makeChromakey(
1183
1203
  );
1184
1204
  }
1185
1205
 
1206
+ /**
1207
+ * Creates a Ken Burns effect (zoom/pan) filter for images
1208
+ * @param inputs - Input stream labels (must be video)
1209
+ * @param options - Ken Burns parameters
1210
+ * - effect: Type of effect (zoom-in, zoom-out, pan-left, pan-right, pan-top, pan-bottom)
1211
+ * - zoom: Zoom percentage (30 = 30%, applies to all effects)
1212
+ * - effectDuration: Duration of the ken burns animation in milliseconds (0 = use fragment duration)
1213
+ * - fragmentDuration: Total duration of the fragment in milliseconds
1214
+ * - easing: Easing function (linear, ease-in, ease-out, ease-in-out)
1215
+ * - width: Output width
1216
+ * - height: Output height
1217
+ * - fps: Output frame rate
1218
+ * - focalX: Focal point X in percent (0-100, for zoom effects)
1219
+ * - focalY: Focal point Y in percent (0-100, for zoom effects)
1220
+ */
1221
+ export function makeKenBurns(
1222
+ inputs: Label[],
1223
+ options: {
1224
+ effect: 'zoom-in' | 'zoom-out' | 'pan-left' | 'pan-right' | 'pan-top' | 'pan-bottom';
1225
+ zoom: number;
1226
+ effectDuration: number;
1227
+ fragmentDuration: number;
1228
+ easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
1229
+ width: number;
1230
+ height: number;
1231
+ fps: number;
1232
+ focalX?: number;
1233
+ focalY?: number;
1234
+ panStartX?: number;
1235
+ panStartY?: number;
1236
+ panEndX?: number;
1237
+ panEndY?: number;
1238
+ },
1239
+ ): Filter {
1240
+ const input = inputs[0];
1241
+
1242
+ if (input.isAudio) {
1243
+ throw new Error(
1244
+ `makeKenBurns: input must be video, got audio (tag: ${input.tag})`,
1245
+ );
1246
+ }
1247
+
1248
+ const output = {
1249
+ tag: getLabel(),
1250
+ isAudio: false,
1251
+ };
1252
+
1253
+ const focalX = options.focalX ?? 50;
1254
+ const focalY = options.focalY ?? 50;
1255
+
1256
+ // Determine animation duration (0 = use fragment duration)
1257
+ const animationDuration = options.effectDuration > 0 ? options.effectDuration : options.fragmentDuration;
1258
+ const animationDurationInSeconds = animationDuration / 1000;
1259
+
1260
+ // Total fragment duration
1261
+ const fragmentDurationInSeconds = options.fragmentDuration / 1000;
1262
+ const totalFrames = Math.floor(fragmentDurationInSeconds * options.fps);
1263
+
1264
+ // Animation frames (how many frames the effect takes)
1265
+ const animationFrames = Math.floor(animationDurationInSeconds * options.fps);
1266
+
1267
+ // Convert zoom percentage to factor (e.g., 30% = 1.3x)
1268
+ const zoomFactor = 1 + options.zoom / 100;
1269
+
1270
+ // Create easing function expression
1271
+ // t = progress from 0 to 1 (on/animationFrames)
1272
+ // Returns eased value from 0 to 1
1273
+ const getEasingExpr = (easing: string): string => {
1274
+ const t = `min(1,on/${animationFrames})`;
1275
+ switch (easing) {
1276
+ case 'ease-in':
1277
+ // Quadratic ease-in: t^2
1278
+ return `pow(${t},2)`;
1279
+ case 'ease-out':
1280
+ // Quadratic ease-out: 1-(1-t)^2
1281
+ return `1-pow(1-(${t}),2)`;
1282
+ case 'ease-in-out':
1283
+ // Quadratic ease-in-out: t<0.5 ? 2*t^2 : 1-2*(1-t)^2
1284
+ return `if(lt(${t},0.5),2*pow(${t},2),1-pow(-2*(${t})+2,2)/2)`;
1285
+ case 'linear':
1286
+ default:
1287
+ return t;
1288
+ }
1289
+ };
1290
+
1291
+ const progress = getEasingExpr(options.easing);
1292
+
1293
+ let zoomExpr: string;
1294
+ let xExpr: string;
1295
+ let yExpr: string;
1296
+
1297
+ switch (options.effect) {
1298
+ case 'zoom-in':
1299
+ // Start at 1.0, zoom to zoomFactor over animation duration, then hold
1300
+ zoomExpr = `'1+(${zoomFactor}-1)*(${progress})'`;
1301
+ xExpr = `'iw*${focalX/100}-iw/zoom/2'`;
1302
+ yExpr = `'ih*${focalY/100}-ih/zoom/2'`;
1303
+ break;
1304
+
1305
+ case 'zoom-out':
1306
+ // Start at zoomFactor, zoom out to 1.0 over animation duration, then hold
1307
+ zoomExpr = `'${zoomFactor}-(${zoomFactor}-1)*(${progress})'`;
1308
+ xExpr = `'iw*${focalX/100}-iw/zoom/2'`;
1309
+ yExpr = `'ih*${focalY/100}-ih/zoom/2'`;
1310
+ break;
1311
+
1312
+ case 'pan-left':
1313
+ case 'pan-right': {
1314
+ // Horizontal panning with custom start/end positions
1315
+ const panStart = (options.panStartX ?? 0) / 100; // 0 = left edge, 1 = right edge
1316
+ const panEnd = (options.panEndX ?? 100) / 100;
1317
+
1318
+ zoomExpr = `'${zoomFactor}'`;
1319
+ // Interpolate between start and end positions: start + (end - start) * progress
1320
+ xExpr = `'(iw-iw/zoom)*(${panStart}+(${panEnd}-${panStart})*(${progress}))'`;
1321
+ yExpr = `'(ih-ih/zoom)/2'`; // center vertically
1322
+ break;
1323
+ }
1324
+
1325
+ case 'pan-top':
1326
+ case 'pan-bottom': {
1327
+ // Vertical panning with custom start/end positions
1328
+ const panStart = (options.panStartY ?? 0) / 100; // 0 = top edge, 1 = bottom edge
1329
+ const panEnd = (options.panEndY ?? 100) / 100;
1330
+
1331
+ zoomExpr = `'${zoomFactor}'`;
1332
+ xExpr = `'(iw-iw/zoom)/2'`; // center horizontally
1333
+ // Interpolate between start and end positions: start + (end - start) * progress
1334
+ yExpr = `'(ih-ih/zoom)*(${panStart}+(${panEnd}-${panStart})*(${progress}))'`;
1335
+ break;
1336
+ }
1337
+ }
1338
+
1339
+ // zoompan filter syntax:
1340
+ // zoompan=z='zoom':x='x':y='y':d=frames:s=WxH:fps=FPS
1341
+ // d=totalFrames ensures we generate frames for the entire fragment duration
1342
+ const filterStr = `zoompan=z=${zoomExpr}:x=${xExpr}:y=${yExpr}:d=${totalFrames}:s=${options.width}x${options.height}:fps=${options.fps}`;
1343
+
1344
+ return new Filter(inputs, [output], filterStr);
1345
+ }
1346
+
1186
1347
  /**
1187
1348
  * Creates a despill filter to remove color spill from chromakey
1188
1349
  * @param inputs - Input stream labels (must be video)