@gannochenko/staticstripes 0.0.21 → 0.0.23

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 (45) hide show
  1. package/dist/app-builder.d.ts +18 -0
  2. package/dist/app-builder.d.ts.map +1 -0
  3. package/dist/app-builder.js +94 -0
  4. package/dist/app-builder.js.map +1 -0
  5. package/dist/cli/commands/filters.d.ts +3 -0
  6. package/dist/cli/commands/filters.d.ts.map +1 -0
  7. package/dist/cli/commands/filters.js +21 -0
  8. package/dist/cli/commands/filters.js.map +1 -0
  9. package/dist/cli/commands/generate.d.ts.map +1 -1
  10. package/dist/cli/commands/generate.js +6 -1
  11. package/dist/cli/commands/generate.js.map +1 -1
  12. package/dist/cli/instagram/instagram-upload-strategy.d.ts +5 -0
  13. package/dist/cli/instagram/instagram-upload-strategy.d.ts.map +1 -1
  14. package/dist/cli/instagram/instagram-upload-strategy.js +46 -3
  15. package/dist/cli/instagram/instagram-upload-strategy.js.map +1 -1
  16. package/dist/cli.js +2 -0
  17. package/dist/cli.js.map +1 -1
  18. package/dist/html-project-parser.d.ts +20 -1
  19. package/dist/html-project-parser.d.ts.map +1 -1
  20. package/dist/html-project-parser.js +128 -15
  21. package/dist/html-project-parser.js.map +1 -1
  22. package/dist/project.d.ts +4 -1
  23. package/dist/project.d.ts.map +1 -1
  24. package/dist/project.js +50 -1
  25. package/dist/project.js.map +1 -1
  26. package/dist/sample-sequences.d.ts.map +1 -1
  27. package/dist/sample-sequences.js +7 -0
  28. package/dist/sample-sequences.js.map +1 -1
  29. package/dist/sequence.d.ts +4 -1
  30. package/dist/sequence.d.ts.map +1 -1
  31. package/dist/sequence.js +43 -19
  32. package/dist/sequence.js.map +1 -1
  33. package/dist/type.d.ts +18 -1
  34. package/dist/type.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/app-builder.ts +113 -0
  37. package/src/cli/commands/filters.ts +21 -0
  38. package/src/cli/commands/generate.ts +10 -1
  39. package/src/cli/instagram/instagram-upload-strategy.ts +61 -1
  40. package/src/cli.ts +2 -0
  41. package/src/html-project-parser.ts +172 -26
  42. package/src/project.ts +62 -0
  43. package/src/sample-sequences.ts +7 -0
  44. package/src/sequence.ts +50 -20
  45. package/src/type.ts +20 -1
package/src/sequence.ts CHANGED
@@ -16,13 +16,14 @@ import {
16
16
  Stream,
17
17
  VisualFilter,
18
18
  } from './stream';
19
- import { Output, SequenceDefinition } from './type';
19
+ import { Output, SequenceDefinition, FragmentDebugInfo } from './type';
20
20
 
21
21
  export class Sequence {
22
22
  private time: number = 0; // time is absolute
23
23
 
24
24
  private videoStream!: Stream;
25
25
  private audioStream!: Stream;
26
+ private debugInfo: FragmentDebugInfo[] = []; // Collect debug info during build
26
27
 
27
28
  constructor(
28
29
  private buf: FilterBuffer,
@@ -44,6 +45,11 @@ export class Sequence {
44
45
  this.expressionContext,
45
46
  );
46
47
 
48
+ const calculatedDuration = calculateFinalValue(
49
+ fragment.duration,
50
+ this.expressionContext,
51
+ );
52
+
47
53
  if (fragment.id === 'outro_message') {
48
54
  debugger;
49
55
  }
@@ -51,7 +57,7 @@ export class Sequence {
51
57
  const timeContext: TimeData = {
52
58
  start: 0,
53
59
  end: 0,
54
- duration: fragment.duration,
60
+ duration: calculatedDuration,
55
61
  };
56
62
 
57
63
  const asset = this.assetManager.getAssetByName(fragment.assetName);
@@ -69,7 +75,7 @@ export class Sequence {
69
75
  } else {
70
76
  // Create blank transparent video stream for audio-only assets
71
77
  currentVideoStream = makeBlankStream(
72
- fragment.duration,
78
+ calculatedDuration,
73
79
  this.output.resolution.width,
74
80
  this.output.resolution.height,
75
81
  this.output.fps,
@@ -78,36 +84,40 @@ export class Sequence {
78
84
  }
79
85
 
80
86
  // Create audio stream: use actual audio if available, otherwise create silent stream
87
+ // If fragment has -sound: off, always use silence
81
88
  let currentAudioStream: Stream;
82
- if (asset.hasAudio) {
89
+ if (fragment.sound === 'off') {
90
+ // Force silent audio when -sound: off
91
+ currentAudioStream = makeSilentStream(calculatedDuration, this.buf);
92
+ } else if (asset.hasAudio) {
83
93
  currentAudioStream = makeStream(
84
94
  this.assetManager.getAudioInputLabelByAssetName(fragment.assetName),
85
95
  this.buf,
86
96
  );
87
97
  } else {
88
98
  // Create silent audio stream matching the video duration
89
- currentAudioStream = makeSilentStream(fragment.duration, this.buf);
99
+ currentAudioStream = makeSilentStream(calculatedDuration, this.buf);
90
100
  }
91
101
 
92
102
  // duration and clipping adjustment
93
- if (fragment.trimLeft != 0 || fragment.duration < asset.duration) {
103
+ if (fragment.trimLeft != 0 || calculatedDuration < asset.duration) {
94
104
  // console.log('fragment.trimLeft=' + fragment.trimLeft);
95
- // console.log('fragment.duration=' + fragment.duration);
105
+ // console.log('fragment.duration=' + calculatedDuration);
96
106
  // console.log('asset.duration=' + asset.duration);
97
107
 
98
108
  // Only trim video if it came from an actual source
99
109
  if (asset.hasVideo) {
100
110
  currentVideoStream.trim(
101
111
  fragment.trimLeft,
102
- fragment.trimLeft + fragment.duration,
112
+ fragment.trimLeft + calculatedDuration,
103
113
  );
104
114
  }
105
115
 
106
- // Only trim audio if it came from an actual source
107
- if (asset.hasAudio) {
116
+ // Only trim audio if it came from an actual source AND sound is not off
117
+ if (asset.hasAudio && fragment.sound !== 'off') {
108
118
  currentAudioStream.trim(
109
119
  fragment.trimLeft,
110
- fragment.trimLeft + fragment.duration,
120
+ fragment.trimLeft + calculatedDuration,
111
121
  );
112
122
  }
113
123
  }
@@ -124,12 +134,12 @@ export class Sequence {
124
134
 
125
135
  if (
126
136
  asset.duration === 0 &&
127
- fragment.duration > 0 &&
137
+ calculatedDuration > 0 &&
128
138
  asset.type === 'image'
129
139
  ) {
130
140
  // special case for images - extend static image to desired duration
131
141
  currentVideoStream.tPad({
132
- start: fragment.duration,
142
+ start: calculatedDuration,
133
143
  startMode: 'clone',
134
144
  });
135
145
  }
@@ -202,7 +212,7 @@ export class Sequence {
202
212
  fades: [
203
213
  {
204
214
  type: 'out',
205
- startTime: fragment.duration - fragment.transitionOutDuration,
215
+ startTime: calculatedDuration - fragment.transitionOutDuration,
206
216
  duration: fragment.transitionOutDuration,
207
217
  },
208
218
  ],
@@ -211,7 +221,7 @@ export class Sequence {
211
221
  fades: [
212
222
  {
213
223
  type: 'out',
214
- startTime: fragment.duration - fragment.transitionOutDuration,
224
+ startTime: calculatedDuration - fragment.transitionOutDuration,
215
225
  duration: fragment.transitionOutDuration,
216
226
  },
217
227
  ],
@@ -239,7 +249,7 @@ export class Sequence {
239
249
 
240
250
  // console.log('this.time=' + this.time);
241
251
  // console.log('streamDuration=' + this.time);
242
- // console.log('otherStreamDuration=' + fragment.duration);
252
+ // console.log('otherStreamDuration=' + calculatedDuration);
243
253
  // console.log('otherStreamOffsetLeft=' + otherStreamOffsetLeft);
244
254
 
245
255
  // use overlay
@@ -247,14 +257,14 @@ export class Sequence {
247
257
  flipLayers: fragment.overlayZIndex < 0,
248
258
  offset: {
249
259
  streamDuration: this.time,
250
- otherStreamDuration: fragment.duration,
260
+ otherStreamDuration: calculatedDuration,
251
261
  otherStreamOffsetLeft: otherStreamOffsetLeft,
252
262
  },
253
263
  });
254
264
  this.audioStream.overlayStream(currentAudioStream, {
255
265
  offset: {
256
266
  streamDuration: this.time,
257
- otherStreamDuration: fragment.duration,
267
+ otherStreamDuration: calculatedDuration,
258
268
  otherStreamOffsetLeft: otherStreamOffsetLeft,
259
269
  },
260
270
  });
@@ -291,13 +301,25 @@ export class Sequence {
291
301
  }
292
302
 
293
303
  timeContext.start = this.time + calculatedOverlayLeft;
294
- timeContext.end = this.time + fragment.duration + calculatedOverlayLeft;
295
- this.time += fragment.duration + calculatedOverlayLeft;
304
+ timeContext.end = this.time + calculatedDuration + calculatedOverlayLeft;
305
+ this.time += calculatedDuration + calculatedOverlayLeft;
296
306
 
297
307
  this.expressionContext.fragments.set(fragment.id, {
298
308
  time: timeContext,
299
309
  });
300
310
 
311
+ // Collect debug info
312
+ this.debugInfo.push({
313
+ id: fragment.id,
314
+ assetName: fragment.assetName,
315
+ startTime: timeContext.start,
316
+ endTime: timeContext.end,
317
+ duration: calculatedDuration,
318
+ trimLeft: fragment.trimLeft,
319
+ overlayLeft: calculatedOverlayLeft,
320
+ enabled: fragment.enabled,
321
+ });
322
+
301
323
  // console.log('new time=' + this.time);
302
324
 
303
325
  firstOne = false;
@@ -327,4 +349,12 @@ export class Sequence {
327
349
  public getAudioStream(): Stream {
328
350
  return this.audioStream;
329
351
  }
352
+
353
+ public getDebugInfo(): FragmentDebugInfo[] {
354
+ return this.debugInfo;
355
+ }
356
+
357
+ public getTotalDuration(): number {
358
+ return this.time;
359
+ }
330
360
  }
package/src/type.ts CHANGED
@@ -47,7 +47,7 @@ export type Fragment = {
47
47
  id: string;
48
48
  enabled: boolean;
49
49
  assetName: string;
50
- duration: number; // calculated, in seconds (can come from CSS or from the asset's duration)
50
+ duration: number | CompiledExpression; // calculated, in seconds (can come from CSS or from the asset's duration, or be a calc() expression)
51
51
  trimLeft: number; // in seconds (skip first N seconds of asset via -trim-start)
52
52
  overlayLeft: number | CompiledExpression; // amount of seconds to overlay with the previous fragment (normalized from margin-left + prev margin-right)
53
53
  overlayZIndex: number;
@@ -66,6 +66,7 @@ export type Fragment = {
66
66
  chromakeySimilarity: number;
67
67
  chromakeyColor: string;
68
68
  visualFilter?: string; // Optional visual filter (e.g., 'instagram-nashville')
69
+ sound: 'on' | 'off'; // Whether to use asset audio or replace with silence (default: 'on')
69
70
  container?: Container; // Optional container attached to this fragment
70
71
  app?: App; // Optional app attached to this fragment
71
72
  timecodeLabel?: string; // Optional label for timecode (from data-timecode attribute)
@@ -75,6 +76,23 @@ export type SequenceDefinition = {
75
76
  fragments: Fragment[];
76
77
  };
77
78
 
79
+ export type FragmentDebugInfo = {
80
+ id: string;
81
+ assetName: string;
82
+ startTime: number; // absolute start time in seconds
83
+ endTime: number; // absolute end time in seconds
84
+ duration: number; // fragment duration in seconds
85
+ trimLeft: number; // trim from asset start in seconds
86
+ overlayLeft: number; // overlay with previous fragment in seconds
87
+ enabled: boolean;
88
+ };
89
+
90
+ export type SequenceDebugInfo = {
91
+ sequenceIndex: number;
92
+ totalDuration: number; // total duration of the sequence in seconds
93
+ fragments: FragmentDebugInfo[];
94
+ };
95
+
78
96
  export type Output = {
79
97
  name: string; // e.g. "youtube"
80
98
  path: string; // e.g. "./output/video.mp4"
@@ -118,6 +136,7 @@ export type Upload = {
118
136
  thumbOffset?: number; // Thumbnail frame location in milliseconds
119
137
  coverUrl?: string; // Optional cover image URL
120
138
  videoUrl?: string; // Public URL to video (if not auto-generated from S3)
139
+ locationId?: string; // Facebook Page location ID for geotagging
121
140
  };
122
141
  };
123
142