@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.
Files changed (86) hide show
  1. package/.prettierrc +8 -0
  2. package/Makefile +69 -0
  3. package/dist/asset-manager.d.ts +16 -0
  4. package/dist/asset-manager.d.ts.map +1 -0
  5. package/dist/asset-manager.js +50 -0
  6. package/dist/asset-manager.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +257 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/container-renderer.d.ts +21 -0
  12. package/dist/container-renderer.d.ts.map +1 -0
  13. package/dist/container-renderer.js +149 -0
  14. package/dist/container-renderer.js.map +1 -0
  15. package/dist/expression-parser.d.ts +63 -0
  16. package/dist/expression-parser.d.ts.map +1 -0
  17. package/dist/expression-parser.js +145 -0
  18. package/dist/expression-parser.js.map +1 -0
  19. package/dist/ffmpeg.d.ts +375 -0
  20. package/dist/ffmpeg.d.ts.map +1 -0
  21. package/dist/ffmpeg.js +997 -0
  22. package/dist/ffmpeg.js.map +1 -0
  23. package/dist/ffprobe.d.ts +2 -0
  24. package/dist/ffprobe.d.ts.map +1 -0
  25. package/dist/ffprobe.js +31 -0
  26. package/dist/ffprobe.js.map +1 -0
  27. package/dist/html-parser.d.ts +56 -0
  28. package/dist/html-parser.d.ts.map +1 -0
  29. package/dist/html-parser.js +208 -0
  30. package/dist/html-parser.js.map +1 -0
  31. package/dist/html-project-parser.d.ts +169 -0
  32. package/dist/html-project-parser.d.ts.map +1 -0
  33. package/dist/html-project-parser.js +954 -0
  34. package/dist/html-project-parser.js.map +1 -0
  35. package/dist/index.d.ts +6 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/label-generator.d.ts +35 -0
  40. package/dist/label-generator.d.ts.map +1 -0
  41. package/dist/label-generator.js +69 -0
  42. package/dist/label-generator.js.map +1 -0
  43. package/dist/project.d.ts +29 -0
  44. package/dist/project.d.ts.map +1 -0
  45. package/dist/project.js +137 -0
  46. package/dist/project.js.map +1 -0
  47. package/dist/sample-sequences.d.ts +5 -0
  48. package/dist/sample-sequences.d.ts.map +1 -0
  49. package/dist/sample-sequences.js +199 -0
  50. package/dist/sample-sequences.js.map +1 -0
  51. package/dist/sample-streams.d.ts +2 -0
  52. package/dist/sample-streams.d.ts.map +1 -0
  53. package/dist/sample-streams.js +109 -0
  54. package/dist/sample-streams.js.map +1 -0
  55. package/dist/sequence.d.ts +21 -0
  56. package/dist/sequence.d.ts.map +1 -0
  57. package/dist/sequence.js +269 -0
  58. package/dist/sequence.js.map +1 -0
  59. package/dist/stream.d.ts +135 -0
  60. package/dist/stream.d.ts.map +1 -0
  61. package/dist/stream.js +779 -0
  62. package/dist/stream.js.map +1 -0
  63. package/dist/type.d.ts +73 -0
  64. package/dist/type.d.ts.map +1 -0
  65. package/dist/type.js +3 -0
  66. package/dist/type.js.map +1 -0
  67. package/eslint.config.js +44 -0
  68. package/package.json +50 -0
  69. package/src/asset-manager.ts +55 -0
  70. package/src/cli.ts +306 -0
  71. package/src/container-renderer.ts +190 -0
  72. package/src/expression-parser.test.ts +459 -0
  73. package/src/expression-parser.ts +199 -0
  74. package/src/ffmpeg.ts +1403 -0
  75. package/src/ffprobe.ts +29 -0
  76. package/src/html-parser.ts +221 -0
  77. package/src/html-project-parser.ts +1195 -0
  78. package/src/index.ts +9 -0
  79. package/src/label-generator.ts +74 -0
  80. package/src/project.ts +180 -0
  81. package/src/sample-sequences.ts +225 -0
  82. package/src/sample-streams.ts +142 -0
  83. package/src/sequence.ts +330 -0
  84. package/src/stream.ts +1012 -0
  85. package/src/type.ts +81 -0
  86. package/tsconfig.json +24 -0
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ // This file is kept for backward compatibility
2
+ // For CLI usage, use the 'staticstripes' command instead
3
+ // Example: npx staticstripes generate -p ./examples/demo
4
+
5
+ export { HTMLParser } from './html-parser.js';
6
+ export { HTMLProjectParser } from './html-project-parser.js';
7
+ export { Project } from './project.js';
8
+ export { makeFFmpegCommand, runFFMpeg } from './ffmpeg.js';
9
+ export { getAssetDuration } from './ffprobe.js';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * LabelGenerator - Generates unique labels for FFmpeg filter graph streams
3
+ * Maintains a ledger of used labels to ensure uniqueness
4
+ */
5
+ export class LabelGenerator {
6
+ private usedLabels: Set<string> = new Set();
7
+
8
+ /**
9
+ * Generates a unique random label
10
+ * Format: [a-z][0-999]
11
+ * If collision occurs, regenerates until unique
12
+ */
13
+ generate(): string {
14
+ let label: string;
15
+ let attempts = 0;
16
+ const maxAttempts = 10000; // Safety limit
17
+
18
+ do {
19
+ label = this.generateRandom();
20
+ attempts++;
21
+
22
+ if (attempts >= maxAttempts) {
23
+ // Fallback: use timestamp-based label to guarantee uniqueness
24
+ label = `t${Date.now()}${Math.random().toString(36).substring(2, 5)}`;
25
+ break;
26
+ }
27
+ } while (this.usedLabels.has(label));
28
+
29
+ this.usedLabels.add(label);
30
+ return label;
31
+ }
32
+
33
+ /**
34
+ * Generates a random label (may not be unique)
35
+ */
36
+ private generateRandom(): string {
37
+ const letter = String.fromCharCode(97 + Math.floor(Math.random() * 26)); // a-z
38
+ const num = Math.floor(Math.random() * 1000);
39
+ return `${letter}${num}`;
40
+ }
41
+
42
+ /**
43
+ * Marks a label as used (for external labels like '0:v', 'outv')
44
+ */
45
+ markUsed(label: string): void {
46
+ this.usedLabels.add(label);
47
+ }
48
+
49
+ /**
50
+ * Checks if a label is already used
51
+ */
52
+ isUsed(label: string): boolean {
53
+ return this.usedLabels.has(label);
54
+ }
55
+
56
+ /**
57
+ * Clears all used labels (for testing or reset)
58
+ */
59
+ clear(): void {
60
+ this.usedLabels.clear();
61
+ }
62
+
63
+ /**
64
+ * Returns the count of used labels
65
+ */
66
+ getUsedCount(): number {
67
+ return this.usedLabels.size;
68
+ }
69
+ }
70
+
71
+ const gen = new LabelGenerator();
72
+
73
+ // fucking singleton
74
+ export const getLabel = () => gen.generate();
package/src/project.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { Asset, Output, SequenceDefinition } from './type';
2
+ import { Label } from './ffmpeg';
3
+ import { AssetManager } from './asset-manager';
4
+ import { Sequence } from './sequence';
5
+ import { FilterBuffer } from './stream';
6
+ import { ExpressionContext, FragmentData } from './expression-parser';
7
+ import { renderContainers } from './container-renderer';
8
+ import { dirname } from 'path';
9
+
10
+ export class Project {
11
+ private assetManager: AssetManager;
12
+ private expressionContext: ExpressionContext;
13
+
14
+ constructor(
15
+ private sequencesDefinitions: SequenceDefinition[],
16
+ assets: Asset[],
17
+ private outputs: Map<string, Output>,
18
+ private cssText: string,
19
+ private projectPath: string,
20
+ ) {
21
+ this.assetManager = new AssetManager(assets);
22
+ this.expressionContext = {
23
+ fragments: new Map<string, FragmentData>(),
24
+ };
25
+ }
26
+
27
+ public async build(outputName: string): Promise<FilterBuffer> {
28
+ const output = this.getOutput(outputName);
29
+ if (!output) {
30
+ throw new Error(`Output "${outputName}" not found`);
31
+ }
32
+
33
+ let buf = new FilterBuffer();
34
+ let mainSequence: Sequence | null = null;
35
+
36
+ this.sequencesDefinitions.forEach((sequenceDefinition) => {
37
+ const seq = new Sequence(
38
+ buf,
39
+ sequenceDefinition,
40
+ output,
41
+ this.getAssetManager(),
42
+ this.expressionContext,
43
+ );
44
+ if (seq.isEmpty()) {
45
+ return;
46
+ }
47
+
48
+ seq.build();
49
+
50
+ if (!mainSequence) {
51
+ mainSequence = seq;
52
+ } else {
53
+ mainSequence.overlayWith(seq);
54
+ }
55
+ });
56
+
57
+ if (mainSequence) {
58
+ const sequence: Sequence = mainSequence;
59
+ sequence.getVideoStream().endTo({
60
+ tag: 'outv',
61
+ isAudio: false,
62
+ });
63
+ sequence.getAudioStream().endTo({
64
+ tag: 'outa',
65
+ isAudio: true,
66
+ });
67
+ }
68
+
69
+ return buf;
70
+ }
71
+
72
+ public printStats() {
73
+ console.log('\n=== Project stats ===\n');
74
+ console.log('== Assets ==\n');
75
+ this.assetManager.getAssetIndexMap().forEach((_index, assetName) => {
76
+ const asset = this.assetManager.getAssetByName(assetName)!;
77
+
78
+ console.log(
79
+ `Asset "${asset.name}" (${asset.type}) dimensions: w=${asset.width}, h=${asset.height}, rotation: ${asset.rotation}°, duration: ${asset.duration}, hasVideo: ${asset.hasVideo}, hasAudio: ${asset.hasAudio}`,
80
+ );
81
+ });
82
+ }
83
+
84
+ public getAssetManager(): AssetManager {
85
+ return this.assetManager;
86
+ }
87
+
88
+ public getOutput(outputName: string): Output | undefined {
89
+ return this.outputs.get(outputName);
90
+ }
91
+
92
+ public getOutputs(): Map<string, Output> {
93
+ return this.outputs;
94
+ }
95
+
96
+ public getCssText(): string {
97
+ return this.cssText;
98
+ }
99
+
100
+ public getSequenceDefinitions(): SequenceDefinition[] {
101
+ return this.sequencesDefinitions;
102
+ }
103
+
104
+ // Delegation methods for convenience
105
+ public getAssetIndexMap(): Map<string, number> {
106
+ return this.assetManager.getAssetIndexMap();
107
+ }
108
+
109
+ public getAssetByName(name: string): Asset | undefined {
110
+ return this.assetManager.getAssetByName(name);
111
+ }
112
+
113
+ public getVideoInputLabelByAssetName(name: string): Label {
114
+ return this.assetManager.getVideoInputLabelByAssetName(name);
115
+ }
116
+
117
+ public getAudioInputLabelByAssetName(name: string): Label {
118
+ return this.assetManager.getAudioInputLabelByAssetName(name);
119
+ }
120
+
121
+ /**
122
+ * Renders all containers and creates virtual assets for them
123
+ */
124
+ public async renderContainers(outputName: string): Promise<void> {
125
+ const output = this.getOutput(outputName);
126
+ if (!output) {
127
+ throw new Error(`Output "${outputName}" not found`);
128
+ }
129
+
130
+ // Collect all fragments with containers
131
+ const fragmentsWithContainers = this.sequencesDefinitions.flatMap((seq) =>
132
+ seq.fragments.filter((frag) => frag.container),
133
+ );
134
+
135
+ if (fragmentsWithContainers.length === 0) {
136
+ return;
137
+ }
138
+
139
+ console.log('\n=== Rendering Containers ===\n');
140
+
141
+ const containers = fragmentsWithContainers.map((frag) => frag.container!);
142
+ const projectDir = dirname(this.projectPath);
143
+
144
+ const results = await renderContainers(
145
+ containers,
146
+ this.cssText,
147
+ output.resolution.width,
148
+ output.resolution.height,
149
+ projectDir,
150
+ );
151
+
152
+ // Create virtual assets and update fragment assetNames
153
+ for (const result of results) {
154
+ const virtualAssetName = result.container.id;
155
+
156
+ // Create virtual asset
157
+ const virtualAsset: Asset = {
158
+ name: virtualAssetName,
159
+ path: result.screenshotPath,
160
+ type: 'image',
161
+ duration: 0,
162
+ width: output.resolution.width,
163
+ height: output.resolution.height,
164
+ rotation: 0,
165
+ hasVideo: true,
166
+ hasAudio: false,
167
+ };
168
+
169
+ this.assetManager.addVirtualAsset(virtualAsset);
170
+
171
+ // Update fragment assetName
172
+ const fragment = fragmentsWithContainers.find(
173
+ (frag) => frag.container?.id === result.container.id,
174
+ );
175
+ if (fragment) {
176
+ fragment.assetName = virtualAssetName;
177
+ }
178
+ }
179
+ }
180
+ }
@@ -0,0 +1,225 @@
1
+ import { ExpressionContext, parseExpression } from './expression-parser';
2
+ import { Project } from './project';
3
+ import { Sequence } from './sequence';
4
+ import { FilterBuffer } from './stream';
5
+
6
+ export const getSampleSequences = (
7
+ project: Project,
8
+ buf: FilterBuffer,
9
+ expressionContext: ExpressionContext,
10
+ outputName: string,
11
+ ) => {
12
+ const output = project.getOutput(outputName);
13
+ if (!output) {
14
+ throw new Error(`Output "${outputName}" not found`);
15
+ }
16
+
17
+ const seq1 = new Sequence(
18
+ buf,
19
+ {
20
+ fragments: [
21
+ {
22
+ id: 'f_01',
23
+ enabled: true,
24
+ assetName: 'intro_image',
25
+ duration: 4000, // asset duration is 0
26
+ trimLeft: 0,
27
+ overlayLeft: 0,
28
+ overlayZIndex: 1,
29
+ transitionIn: '',
30
+ transitionInDuration: 0,
31
+ transitionOut: 'fade',
32
+ transitionOutDuration: 500,
33
+ objectFit: 'cover',
34
+ objectFitContain: 'ambient',
35
+ objectFitContainAmbientBlurStrength: 25,
36
+ objectFitContainAmbientBrightness: -0.1,
37
+ objectFitContainAmbientSaturation: 0.7,
38
+ objectFitContainPillarboxColor: '#000000',
39
+ chromakey: false,
40
+ chromakeyBlend: 0.1,
41
+ chromakeySimilarity: 0.1,
42
+ chromakeyColor: '#000000',
43
+ },
44
+ {
45
+ id: 'f_02',
46
+ enabled: true,
47
+ assetName: 'clip_01',
48
+ duration: 11330 - 3000, // asset duration is 11330
49
+ trimLeft: 3000,
50
+ overlayLeft: 0,
51
+ overlayZIndex: 1,
52
+ transitionIn: 'fade',
53
+ transitionInDuration: 1000,
54
+ transitionOut: '',
55
+ transitionOutDuration: 0,
56
+ objectFit: 'contain',
57
+ objectFitContain: 'ambient',
58
+ objectFitContainAmbientBlurStrength: 25,
59
+ objectFitContainAmbientBrightness: -0.1,
60
+ objectFitContainAmbientSaturation: 0.7,
61
+ objectFitContainPillarboxColor: '#000000',
62
+ chromakey: false,
63
+ chromakeyBlend: 0.1,
64
+ chromakeySimilarity: 0.1,
65
+ chromakeyColor: '#000000',
66
+ },
67
+ {
68
+ id: 'f_03',
69
+ enabled: true,
70
+ assetName: 'glitch',
71
+ duration: 500, // asset duration is 10000
72
+ trimLeft: 0,
73
+ overlayLeft: -250,
74
+ overlayZIndex: 1,
75
+ transitionIn: '',
76
+ transitionInDuration: 0,
77
+ transitionOut: '',
78
+ transitionOutDuration: 0,
79
+ objectFit: 'cover',
80
+ objectFitContain: 'pillarbox',
81
+ objectFitContainAmbientBlurStrength: 25,
82
+ objectFitContainAmbientBrightness: -0.1,
83
+ objectFitContainAmbientSaturation: 0.7,
84
+ objectFitContainPillarboxColor: '#000000',
85
+ chromakey: true,
86
+ chromakeyBlend: 0.1,
87
+ chromakeySimilarity: 0.1,
88
+ chromakeyColor: '#000000',
89
+ },
90
+ {
91
+ id: 'f_04',
92
+ enabled: true,
93
+ assetName: 'clip_02',
94
+ duration: 3000, // asset duration is 90245
95
+ trimLeft: 0,
96
+ overlayLeft: -250,
97
+ overlayZIndex: -1,
98
+ transitionIn: '',
99
+ transitionInDuration: 0,
100
+ transitionOut: 'fade',
101
+ transitionOutDuration: 1000,
102
+ objectFit: 'contain',
103
+ objectFitContain: 'ambient',
104
+ objectFitContainAmbientBlurStrength: 25,
105
+ objectFitContainAmbientBrightness: -0.1,
106
+ objectFitContainAmbientSaturation: 0.7,
107
+ objectFitContainPillarboxColor: '#000000',
108
+ chromakey: false,
109
+ chromakeyBlend: 0.1,
110
+ chromakeySimilarity: 0.1,
111
+ chromakeyColor: '#000000',
112
+ },
113
+ {
114
+ id: 'ending_screen',
115
+ enabled: true,
116
+ assetName: 'intro_image',
117
+ duration: 4000, // asset duration is 0
118
+ trimLeft: 0,
119
+ overlayLeft: 0,
120
+ overlayZIndex: 1,
121
+ transitionIn: '',
122
+ transitionInDuration: 0,
123
+ transitionOut: 'fade',
124
+ transitionOutDuration: 500,
125
+ objectFit: 'cover',
126
+ objectFitContain: 'ambient',
127
+ objectFitContainAmbientBlurStrength: 25,
128
+ objectFitContainAmbientBrightness: -0.1,
129
+ objectFitContainAmbientSaturation: 0.7,
130
+ objectFitContainPillarboxColor: '#000000',
131
+ chromakey: false,
132
+ chromakeyBlend: 0.1,
133
+ chromakeySimilarity: 0.1,
134
+ chromakeyColor: '#000000',
135
+ },
136
+ ],
137
+ },
138
+ output,
139
+ project.getAssetManager(),
140
+ expressionContext,
141
+ );
142
+ seq1.build();
143
+
144
+ const seq2 = new Sequence(
145
+ buf,
146
+ {
147
+ fragments: [
148
+ {
149
+ id: 'f_06',
150
+ enabled: true,
151
+ assetName: 'guitar_music',
152
+ duration: 4000, // asset duration is 224653
153
+ trimLeft: 0,
154
+ overlayLeft: 0,
155
+ overlayZIndex: 1,
156
+ transitionIn: '',
157
+ transitionInDuration: 0,
158
+ transitionOut: 'fade',
159
+ transitionOutDuration: 500,
160
+ objectFit: 'cover',
161
+ objectFitContain: 'ambient',
162
+ objectFitContainAmbientBlurStrength: 25,
163
+ objectFitContainAmbientBrightness: -0.1,
164
+ objectFitContainAmbientSaturation: 0.7,
165
+ objectFitContainPillarboxColor: '#000000',
166
+ chromakey: false,
167
+ chromakeyBlend: 0.1,
168
+ chromakeySimilarity: 0.1,
169
+ chromakeyColor: '#000000',
170
+ },
171
+ ],
172
+ },
173
+ output,
174
+ project.getAssetManager(),
175
+ expressionContext,
176
+ );
177
+ seq2.build();
178
+
179
+ const seq3 = new Sequence(
180
+ buf,
181
+ {
182
+ fragments: [
183
+ {
184
+ id: 'end_music',
185
+ enabled: true,
186
+ assetName: 'guitar_music',
187
+ duration: 4000, // asset duration is 224653
188
+ trimLeft: 0,
189
+ overlayLeft: parseExpression('calc(#ending_screen.time.start)'),
190
+ overlayZIndex: 1,
191
+ transitionIn: '',
192
+ transitionInDuration: 0,
193
+ transitionOut: 'fade',
194
+ transitionOutDuration: 500,
195
+ objectFit: 'cover',
196
+ objectFitContain: 'ambient',
197
+ objectFitContainAmbientBlurStrength: 25,
198
+ objectFitContainAmbientBrightness: -0.1,
199
+ objectFitContainAmbientSaturation: 0.7,
200
+ objectFitContainPillarboxColor: '#000000',
201
+ chromakey: false,
202
+ chromakeyBlend: 0.1,
203
+ chromakeySimilarity: 0.1,
204
+ chromakeyColor: '#000000',
205
+ },
206
+ ],
207
+ },
208
+ output,
209
+ project.getAssetManager(),
210
+ expressionContext,
211
+ );
212
+ seq3.build();
213
+
214
+ seq1.overlayWith(seq2);
215
+ seq1.overlayWith(seq3);
216
+
217
+ seq1.getVideoStream().endTo({
218
+ tag: 'outv',
219
+ isAudio: false,
220
+ });
221
+ seq1.getAudioStream().endTo({
222
+ tag: 'outa',
223
+ isAudio: true,
224
+ });
225
+ };
@@ -0,0 +1,142 @@
1
+ import { Project } from './project';
2
+ import {
3
+ ChromakeyBlend,
4
+ ChromakeySimilarity,
5
+ FilterBuffer,
6
+ makeStream,
7
+ } from './stream';
8
+
9
+ // @ts-expect-error unused
10
+ const addSampleStreams = (project: Project, buf: FilterBuffer) => {
11
+ const glitchStream = makeStream(
12
+ project.getAssetManager().getVideoInputLabelByAssetName('glitch'),
13
+ buf,
14
+ )
15
+ .trim(0, 2)
16
+ .fitOutputContain({ width: 1920, height: 1080 })
17
+ .fps(30)
18
+ .chromakey({
19
+ blend: ChromakeyBlend.Smooth,
20
+ similarity: ChromakeySimilarity.Strict,
21
+ color: '#000000',
22
+ });
23
+
24
+ const clip02VideoStream = makeStream(
25
+ project.getAssetManager().getVideoInputLabelByAssetName('clip_02'),
26
+ buf,
27
+ )
28
+ .trim(0, 5)
29
+ .fitOutputContain(
30
+ {
31
+ width: 1920,
32
+ height: 1080,
33
+ },
34
+ {
35
+ ambient: {
36
+ blurStrength: 25,
37
+ brightness: -0.1,
38
+ saturation: 0.7,
39
+ },
40
+ },
41
+ )
42
+ .fps(30);
43
+
44
+ const introImageStream = makeStream(
45
+ project.getAssetManager().getVideoInputLabelByAssetName('intro_image'),
46
+ buf,
47
+ )
48
+ .fps(30)
49
+ .fitOutputCover({ width: 1920, height: 1080 })
50
+ .tPad({
51
+ start: 3,
52
+ startMode: 'clone',
53
+ });
54
+
55
+ makeStream(
56
+ project.getAssetManager().getVideoInputLabelByAssetName('clip_01'),
57
+ buf,
58
+ )
59
+ .trim(0, 5) // length = 5
60
+ .fitOutputContain(
61
+ {
62
+ width: 1920,
63
+ height: 1080,
64
+ },
65
+ {
66
+ ambient: {
67
+ blurStrength: 25,
68
+ brightness: -0.1,
69
+ saturation: 0.7,
70
+ },
71
+ // pillarbox: {
72
+ // color: '#ff0000',
73
+ // },
74
+ },
75
+ )
76
+ .fps(30)
77
+ .fade({
78
+ fades: [
79
+ {
80
+ type: 'in',
81
+ startTime: 0,
82
+ duration: 1,
83
+ },
84
+ ],
85
+ })
86
+ .overlayStream(glitchStream, {
87
+ // length = 5
88
+ offset: {
89
+ streamDuration: 5, // value from trim()
90
+ otherStreamDuration: 2, // value from trim() of glitch
91
+ otherStreamOffsetLeft: 4, // start of the glitch
92
+ },
93
+ })
94
+ .overlayStream(clip02VideoStream, {
95
+ // length = 11 ?
96
+ flipLayers: true,
97
+ offset: {
98
+ streamDuration: 5, // value from trim()
99
+ otherStreamDuration: 6, // 5 seconds of clip01, 1 second of glitch
100
+ otherStreamOffsetLeft: 5, // start of the of clip01stream
101
+ },
102
+ })
103
+ .fade({
104
+ fades: [
105
+ {
106
+ type: 'out',
107
+ startTime: 9,
108
+ duration: 1,
109
+ },
110
+ ],
111
+ })
112
+ .concatStream(introImageStream)
113
+ .endTo({
114
+ tag: 'outv',
115
+ isAudio: false,
116
+ });
117
+
118
+ const clip02AudioStream = makeStream(
119
+ project.getAssetManager().getAudioInputLabelByAssetName('clip_02'),
120
+ buf,
121
+ ).trim(0, 5);
122
+
123
+ makeStream(
124
+ project.getAssetManager().getAudioInputLabelByAssetName('clip_01'),
125
+ buf,
126
+ )
127
+ .trim(0, 5)
128
+ .fade({
129
+ fades: [
130
+ {
131
+ type: 'in',
132
+ startTime: 0,
133
+ duration: 1,
134
+ },
135
+ ],
136
+ })
137
+ .concatStream(clip02AudioStream)
138
+ .endTo({
139
+ tag: 'outa',
140
+ isAudio: true,
141
+ });
142
+ };