@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.
- package/Makefile +8 -0
- package/dist/app-builder.d.ts +18 -0
- package/dist/app-builder.d.ts.map +1 -0
- package/dist/app-builder.js +94 -0
- package/dist/app-builder.js.map +1 -0
- package/dist/cli/commands/filters.d.ts +3 -0
- package/dist/cli/commands/filters.d.ts.map +1 -0
- package/dist/cli/commands/filters.js +21 -0
- package/dist/cli/commands/filters.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +6 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/instagram/instagram-upload-strategy.d.ts +5 -0
- package/dist/cli/instagram/instagram-upload-strategy.d.ts.map +1 -1
- package/dist/cli/instagram/instagram-upload-strategy.js +46 -3
- package/dist/cli/instagram/instagram-upload-strategy.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/ffmpeg.d.ts +32 -0
- package/dist/ffmpeg.d.ts.map +1 -1
- package/dist/ffmpeg.js +118 -0
- package/dist/ffmpeg.js.map +1 -1
- package/dist/html-project-parser.d.ts +36 -1
- package/dist/html-project-parser.d.ts.map +1 -1
- package/dist/html-project-parser.js +332 -15
- package/dist/html-project-parser.js.map +1 -1
- package/dist/project.d.ts +4 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +50 -1
- package/dist/project.js.map +1 -1
- package/dist/sample-sequences.d.ts.map +1 -1
- package/dist/sample-sequences.js +293 -0
- package/dist/sample-sequences.js.map +1 -1
- package/dist/sequence.d.ts +4 -1
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +71 -21
- package/dist/sequence.js.map +1 -1
- package/dist/stream.d.ts +17 -0
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +28 -0
- package/dist/stream.js.map +1 -1
- package/dist/type.d.ts +29 -2
- package/dist/type.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/app-builder.ts +113 -0
- package/src/cli/commands/filters.ts +21 -0
- package/src/cli/commands/generate.ts +10 -1
- package/src/cli/instagram/instagram-upload-strategy.ts +61 -1
- package/src/cli.ts +2 -0
- package/src/ffmpeg.ts +161 -0
- package/src/html-project-parser.ts +410 -28
- package/src/project.ts +62 -0
- package/src/sample-sequences.ts +300 -0
- package/src/sequence.ts +78 -22
- package/src/stream.ts +50 -0
- 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)
|