@meframe/core 0.0.2 → 0.0.4

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 (193) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +6 -4
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/cache/CacheManager.d.ts +2 -2
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +4 -3
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/cache/l1/VideoL1Cache.d.ts +2 -2
  9. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  10. package/dist/cache/l1/VideoL1Cache.js +13 -8
  11. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  12. package/dist/config/defaults.d.ts.map +1 -1
  13. package/dist/config/defaults.js +3 -1
  14. package/dist/config/defaults.js.map +1 -1
  15. package/dist/config/types.d.ts +6 -0
  16. package/dist/config/types.d.ts.map +1 -1
  17. package/dist/controllers/PlaybackController.d.ts +7 -8
  18. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  19. package/dist/controllers/PlaybackController.js +56 -76
  20. package/dist/controllers/PlaybackController.js.map +1 -1
  21. package/dist/controllers/types.d.ts +2 -3
  22. package/dist/controllers/types.d.ts.map +1 -1
  23. package/dist/event/events.d.ts +1 -4
  24. package/dist/event/events.d.ts.map +1 -1
  25. package/dist/event/events.js.map +1 -1
  26. package/dist/model/CompositionModel.d.ts +1 -0
  27. package/dist/model/CompositionModel.d.ts.map +1 -1
  28. package/dist/model/CompositionModel.js +2 -0
  29. package/dist/model/CompositionModel.js.map +1 -1
  30. package/dist/model/patch.d.ts +6 -2
  31. package/dist/model/patch.d.ts.map +1 -1
  32. package/dist/model/patch.js +76 -2
  33. package/dist/model/patch.js.map +1 -1
  34. package/dist/model/types.d.ts +1 -0
  35. package/dist/model/types.d.ts.map +1 -1
  36. package/dist/orchestrator/CompositionPlanner.d.ts +8 -7
  37. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  38. package/dist/orchestrator/CompositionPlanner.js +33 -56
  39. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  40. package/dist/orchestrator/Orchestrator.d.ts +0 -1
  41. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  42. package/dist/orchestrator/Orchestrator.js +40 -19
  43. package/dist/orchestrator/Orchestrator.js.map +1 -1
  44. package/dist/orchestrator/VideoClipSession.d.ts +3 -5
  45. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  46. package/dist/orchestrator/VideoClipSession.js +66 -69
  47. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  48. package/dist/orchestrator/types.d.ts +2 -0
  49. package/dist/orchestrator/types.d.ts.map +1 -1
  50. package/dist/stages/compose/GlobalAudioSession.d.ts +6 -0
  51. package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
  52. package/dist/stages/compose/GlobalAudioSession.js +17 -1
  53. package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
  54. package/dist/stages/compose/types.d.ts +2 -1
  55. package/dist/stages/compose/types.d.ts.map +1 -1
  56. package/dist/stages/demux/MP4Demuxer.d.ts +0 -1
  57. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  58. package/dist/stages/load/ResourceLoader.d.ts +22 -1
  59. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  60. package/dist/stages/load/ResourceLoader.js +71 -25
  61. package/dist/stages/load/ResourceLoader.js.map +1 -1
  62. package/dist/stages/load/TaskManager.d.ts +1 -1
  63. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  64. package/dist/stages/load/TaskManager.js +3 -2
  65. package/dist/stages/load/TaskManager.js.map +1 -1
  66. package/dist/stages/load/types.d.ts +2 -0
  67. package/dist/stages/load/types.d.ts.map +1 -1
  68. package/dist/utils/time-utils.d.ts +3 -2
  69. package/dist/utils/time-utils.d.ts.map +1 -1
  70. package/dist/utils/time-utils.js +2 -1
  71. package/dist/utils/time-utils.js.map +1 -1
  72. package/dist/vite-plugin.d.ts +5 -3
  73. package/dist/vite-plugin.d.ts.map +1 -1
  74. package/dist/vite-plugin.js +109 -52
  75. package/dist/vite-plugin.js.map +1 -1
  76. package/dist/worker/WorkerPool.d.ts +9 -0
  77. package/dist/worker/WorkerPool.d.ts.map +1 -1
  78. package/dist/worker/WorkerPool.js +32 -5
  79. package/dist/worker/WorkerPool.js.map +1 -1
  80. package/dist/{stages/demux → workers}/MP4Demuxer.js +4 -13
  81. package/dist/workers/MP4Demuxer.js.map +1 -0
  82. package/dist/workers/WorkerChannel.js +486 -0
  83. package/dist/workers/WorkerChannel.js.map +1 -0
  84. package/dist/{assets/video-demux.worker-D019I7GQ.js → workers/mp4box.all.js} +4 -912
  85. package/dist/workers/mp4box.all.js.map +1 -0
  86. package/dist/{assets/audio-compose.worker-nGVvHD5Q.js → workers/stages/compose/audio-compose.worker.js} +7 -481
  87. package/dist/workers/stages/compose/audio-compose.worker.js.map +1 -0
  88. package/dist/{assets/video-compose.worker-DPzsC21d.js → workers/stages/compose/video-compose.worker.js} +120 -562
  89. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -0
  90. package/dist/{assets/decode.worker-DpWHsc7R.js → workers/stages/decode/decode.worker.js} +7 -481
  91. package/dist/workers/stages/decode/decode.worker.js.map +1 -0
  92. package/dist/{stages → workers/stages}/demux/audio-demux.worker.js +184 -4
  93. package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -0
  94. package/dist/{stages → workers/stages}/demux/video-demux.worker.js +2 -3
  95. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -0
  96. package/dist/{stages → workers/stages}/encode/encode.worker.js +238 -4
  97. package/dist/workers/stages/encode/encode.worker.js.map +1 -0
  98. package/dist/{stages/mux/MP4Muxer.js → workers/stages/mux/mux.worker.js} +244 -5
  99. package/dist/workers/stages/mux/mux.worker.js.map +1 -0
  100. package/package.json +21 -21
  101. package/dist/assets/audio-compose.worker-nGVvHD5Q.js.map +0 -1
  102. package/dist/assets/audio-demux.worker-xwWBtbAe.js +0 -8299
  103. package/dist/assets/audio-demux.worker-xwWBtbAe.js.map +0 -1
  104. package/dist/assets/decode.worker-DpWHsc7R.js.map +0 -1
  105. package/dist/assets/encode.worker-nfOb3kw6.js +0 -1026
  106. package/dist/assets/encode.worker-nfOb3kw6.js.map +0 -1
  107. package/dist/assets/mux.worker-uEMQY066.js +0 -8019
  108. package/dist/assets/mux.worker-uEMQY066.js.map +0 -1
  109. package/dist/assets/video-compose.worker-DPzsC21d.js.map +0 -1
  110. package/dist/assets/video-demux.worker-D019I7GQ.js.map +0 -1
  111. package/dist/controllers/PreviewHandle.d.ts +0 -25
  112. package/dist/controllers/PreviewHandle.d.ts.map +0 -1
  113. package/dist/controllers/PreviewHandle.js +0 -45
  114. package/dist/controllers/PreviewHandle.js.map +0 -1
  115. package/dist/model/dirty-range.js +0 -220
  116. package/dist/model/dirty-range.js.map +0 -1
  117. package/dist/model/types.js +0 -5
  118. package/dist/model/types.js.map +0 -1
  119. package/dist/plugins/BackpressureMonitor.js +0 -62
  120. package/dist/plugins/BackpressureMonitor.js.map +0 -1
  121. package/dist/stages/compose/AudioDucker.js +0 -161
  122. package/dist/stages/compose/AudioDucker.js.map +0 -1
  123. package/dist/stages/compose/AudioMixer.js +0 -373
  124. package/dist/stages/compose/AudioMixer.js.map +0 -1
  125. package/dist/stages/compose/FilterProcessor.js +0 -226
  126. package/dist/stages/compose/FilterProcessor.js.map +0 -1
  127. package/dist/stages/compose/LayerRenderer.js +0 -215
  128. package/dist/stages/compose/LayerRenderer.js.map +0 -1
  129. package/dist/stages/compose/TransitionProcessor.js +0 -189
  130. package/dist/stages/compose/TransitionProcessor.js.map +0 -1
  131. package/dist/stages/compose/VideoComposer.js +0 -186
  132. package/dist/stages/compose/VideoComposer.js.map +0 -1
  133. package/dist/stages/compose/audio-compose.worker.d.ts +0 -79
  134. package/dist/stages/compose/audio-compose.worker.d.ts.map +0 -1
  135. package/dist/stages/compose/audio-compose.worker.js +0 -540
  136. package/dist/stages/compose/audio-compose.worker.js.map +0 -1
  137. package/dist/stages/compose/audio-compose.worker2.js +0 -5
  138. package/dist/stages/compose/audio-compose.worker2.js.map +0 -1
  139. package/dist/stages/compose/video-compose.worker.d.ts +0 -60
  140. package/dist/stages/compose/video-compose.worker.d.ts.map +0 -1
  141. package/dist/stages/compose/video-compose.worker.js +0 -379
  142. package/dist/stages/compose/video-compose.worker.js.map +0 -1
  143. package/dist/stages/compose/video-compose.worker2.js +0 -5
  144. package/dist/stages/compose/video-compose.worker2.js.map +0 -1
  145. package/dist/stages/decode/AudioChunkDecoder.js +0 -82
  146. package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
  147. package/dist/stages/decode/BaseDecoder.js +0 -130
  148. package/dist/stages/decode/BaseDecoder.js.map +0 -1
  149. package/dist/stages/decode/VideoChunkDecoder.js +0 -199
  150. package/dist/stages/decode/VideoChunkDecoder.js.map +0 -1
  151. package/dist/stages/decode/decode.worker.d.ts +0 -70
  152. package/dist/stages/decode/decode.worker.d.ts.map +0 -1
  153. package/dist/stages/decode/decode.worker.js +0 -423
  154. package/dist/stages/decode/decode.worker.js.map +0 -1
  155. package/dist/stages/decode/decode.worker2.js +0 -5
  156. package/dist/stages/decode/decode.worker2.js.map +0 -1
  157. package/dist/stages/demux/MP3FrameParser.js +0 -186
  158. package/dist/stages/demux/MP3FrameParser.js.map +0 -1
  159. package/dist/stages/demux/MP4Demuxer.js.map +0 -1
  160. package/dist/stages/demux/audio-demux.worker.d.ts +0 -51
  161. package/dist/stages/demux/audio-demux.worker.d.ts.map +0 -1
  162. package/dist/stages/demux/audio-demux.worker.js.map +0 -1
  163. package/dist/stages/demux/audio-demux.worker2.js +0 -5
  164. package/dist/stages/demux/audio-demux.worker2.js.map +0 -1
  165. package/dist/stages/demux/video-demux.worker.d.ts +0 -51
  166. package/dist/stages/demux/video-demux.worker.d.ts.map +0 -1
  167. package/dist/stages/demux/video-demux.worker.js.map +0 -1
  168. package/dist/stages/demux/video-demux.worker2.js +0 -5
  169. package/dist/stages/demux/video-demux.worker2.js.map +0 -1
  170. package/dist/stages/encode/AudioChunkEncoder.js +0 -37
  171. package/dist/stages/encode/AudioChunkEncoder.js.map +0 -1
  172. package/dist/stages/encode/BaseEncoder.js +0 -164
  173. package/dist/stages/encode/BaseEncoder.js.map +0 -1
  174. package/dist/stages/encode/VideoChunkEncoder.js +0 -50
  175. package/dist/stages/encode/VideoChunkEncoder.js.map +0 -1
  176. package/dist/stages/encode/encode.worker.d.ts +0 -3
  177. package/dist/stages/encode/encode.worker.d.ts.map +0 -1
  178. package/dist/stages/encode/encode.worker.js.map +0 -1
  179. package/dist/stages/encode/encode.worker2.js +0 -5
  180. package/dist/stages/encode/encode.worker2.js.map +0 -1
  181. package/dist/stages/mux/MP4Muxer.js.map +0 -1
  182. package/dist/stages/mux/mux.worker.d.ts +0 -65
  183. package/dist/stages/mux/mux.worker.d.ts.map +0 -1
  184. package/dist/stages/mux/mux.worker.js +0 -219
  185. package/dist/stages/mux/mux.worker.js.map +0 -1
  186. package/dist/stages/mux/mux.worker2.js +0 -5
  187. package/dist/stages/mux/mux.worker2.js.map +0 -1
  188. package/dist/stages/mux/utils.js +0 -34
  189. package/dist/stages/mux/utils.js.map +0 -1
  190. package/dist/worker/worker-registry.d.ts +0 -12
  191. package/dist/worker/worker-registry.d.ts.map +0 -1
  192. package/dist/worker/worker-registry.js +0 -20
  193. package/dist/worker/worker-registry.js.map +0 -1
@@ -1,215 +0,0 @@
1
- class LayerRenderer {
2
- ctx;
3
- width;
4
- height;
5
- constructor(ctx, width, height) {
6
- this.ctx = ctx;
7
- this.width = width;
8
- this.height = height;
9
- this.ensureHighQualityRendering();
10
- }
11
- ensureHighQualityRendering() {
12
- this.ctx.imageSmoothingEnabled = true;
13
- this.ctx.imageSmoothingQuality = "high";
14
- }
15
- /**
16
- * Render a single layer with all its properties
17
- */
18
- async renderLayer(layer) {
19
- if (!layer.visible || layer.opacity <= 0) return;
20
- this.ctx.save();
21
- try {
22
- this.ensureHighQualityRendering();
23
- this.ctx.globalAlpha = layer.opacity;
24
- if (layer.blendMode) {
25
- this.ctx.globalCompositeOperation = layer.blendMode;
26
- }
27
- if (layer.transform) {
28
- this.applyTransform(layer.transform);
29
- }
30
- switch (layer.type) {
31
- case "video":
32
- await this.renderVideoLayer(layer);
33
- break;
34
- case "image":
35
- await this.renderImageLayer(layer);
36
- break;
37
- case "text":
38
- await this.renderTextLayer(layer);
39
- break;
40
- }
41
- if (layer.mask) {
42
- this.applyMask(layer.mask);
43
- }
44
- } finally {
45
- this.ctx.restore();
46
- }
47
- }
48
- applyTransform(transform) {
49
- const centerX = this.width * (transform.anchorX ?? 0.5);
50
- const centerY = this.height * (transform.anchorY ?? 0.5);
51
- this.ctx.translate(transform.x + centerX, transform.y + centerY);
52
- if (transform.rotation) {
53
- this.ctx.rotate(transform.rotation);
54
- }
55
- this.ctx.scale(transform.scaleX, transform.scaleY);
56
- if (transform.skewX || transform.skewY) {
57
- this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);
58
- }
59
- this.ctx.translate(-centerX, -centerY);
60
- }
61
- async renderVideoLayer(layer) {
62
- const { videoFrame, crop } = layer;
63
- const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;
64
- const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;
65
- const scaleX = this.width / videoWidth;
66
- const scaleY = this.height / videoHeight;
67
- const scale = Math.min(scaleX, scaleY);
68
- const renderWidth = Math.round(videoWidth * scale);
69
- const renderHeight = Math.round(videoHeight * scale);
70
- const renderX = Math.round((this.width - renderWidth) / 2);
71
- const renderY = Math.round((this.height - renderHeight) / 2);
72
- if (crop) {
73
- this.ctx.drawImage(
74
- videoFrame,
75
- crop.x,
76
- crop.y,
77
- crop.width,
78
- crop.height,
79
- renderX,
80
- renderY,
81
- renderWidth,
82
- renderHeight
83
- );
84
- } else {
85
- this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);
86
- }
87
- }
88
- async renderImageLayer(layer) {
89
- const { source, crop } = layer;
90
- if (source instanceof ImageData) {
91
- if (crop) {
92
- const tempCanvas = new OffscreenCanvas(crop.width, crop.height);
93
- const tempCtx = tempCanvas.getContext("2d");
94
- tempCtx.putImageData(source, -crop.x, -crop.y);
95
- this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);
96
- } else {
97
- this.ctx.putImageData(source, 0, 0);
98
- }
99
- } else {
100
- if (!source) {
101
- return;
102
- }
103
- if (crop) {
104
- this.ctx.drawImage(
105
- source,
106
- crop.x,
107
- crop.y,
108
- crop.width,
109
- crop.height,
110
- 0,
111
- 0,
112
- this.width,
113
- this.height
114
- );
115
- } else {
116
- this.ctx.drawImage(source, 0, 0, this.width, this.height);
117
- }
118
- }
119
- }
120
- async renderTextLayer(layer) {
121
- const fontSize = layer.fontSize ?? 16;
122
- const fontFamily = layer.fontFamily ?? "sans-serif";
123
- const fontWeight = layer.fontWeight ?? "normal";
124
- const fontStyle = layer.fontStyle ?? "normal";
125
- this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
126
- this.ctx.fillStyle = layer.color ?? "#000000";
127
- this.ctx.textAlign = layer.textAlign ?? "left";
128
- this.ctx.textBaseline = layer.verticalAlign ?? "top";
129
- if (layer.letterSpacing && typeof this.ctx.letterSpacing !== "undefined") {
130
- this.ctx.letterSpacing = `${layer.letterSpacing}px`;
131
- }
132
- this.ensureHighQualityRendering();
133
- const baseX = this.calculateTextX(layer.textAlign);
134
- const baseY = this.calculateTextY(layer.verticalAlign, fontSize);
135
- const x = Math.round(baseX) + 0.5;
136
- const y = Math.round(baseY) + 0.5;
137
- if (layer.shadow) {
138
- this.ctx.shadowColor = layer.shadow.color;
139
- this.ctx.shadowOffsetX = layer.shadow.offsetX;
140
- this.ctx.shadowOffsetY = layer.shadow.offsetY;
141
- this.ctx.shadowBlur = layer.shadow.blur;
142
- }
143
- if (layer.strokeColor && layer.strokeWidth && layer.strokeWidth > 0) {
144
- this.drawEnhancedStroke(layer.text, x, y, layer.strokeColor, layer.strokeWidth);
145
- }
146
- this.ctx.fillText(layer.text, x, y);
147
- if (layer.shadow) {
148
- this.ctx.shadowColor = "transparent";
149
- this.ctx.shadowOffsetX = 0;
150
- this.ctx.shadowOffsetY = 0;
151
- this.ctx.shadowBlur = 0;
152
- }
153
- }
154
- /**
155
- * Draw enhanced multi-layer stroke for better text visibility
156
- */
157
- drawEnhancedStroke(text, x, y, strokeColor, strokeWidth) {
158
- this.ctx.save();
159
- this.ctx.strokeStyle = strokeColor;
160
- this.ctx.lineJoin = "round";
161
- this.ctx.lineCap = "round";
162
- this.ctx.miterLimit = 2;
163
- const layers = [1.1, 1];
164
- layers.forEach((multiplier) => {
165
- this.ctx.lineWidth = strokeWidth * multiplier;
166
- this.ctx.strokeText(text, x, y);
167
- });
168
- this.ctx.restore();
169
- }
170
- calculateTextX(align) {
171
- switch (align) {
172
- case "center":
173
- return this.width / 2;
174
- case "right":
175
- return this.width;
176
- default:
177
- return 0;
178
- }
179
- }
180
- calculateTextY(align, fontSize = 16) {
181
- switch (align) {
182
- case "middle":
183
- return this.height / 2;
184
- case "bottom":
185
- return this.height * 0.85;
186
- default:
187
- return fontSize;
188
- }
189
- }
190
- applyMask(mask) {
191
- this.ctx.globalCompositeOperation = mask.invert ? "source-out" : "destination-in";
192
- if (mask.source) {
193
- this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);
194
- } else if (mask.shape === "circle") {
195
- this.ctx.beginPath();
196
- this.ctx.arc(
197
- this.width / 2,
198
- this.height / 2,
199
- Math.min(this.width, this.height) / 2,
200
- 0,
201
- Math.PI * 2
202
- );
203
- this.ctx.fill();
204
- }
205
- }
206
- updateDimensions(width, height) {
207
- this.width = width;
208
- this.height = height;
209
- this.ensureHighQualityRendering();
210
- }
211
- }
212
- export {
213
- LayerRenderer
214
- };
215
- //# sourceMappingURL=LayerRenderer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"LayerRenderer.js","sources":["../../../src/stages/compose/LayerRenderer.ts"],"sourcesContent":["import type { Layer, VideoLayer, ImageLayer, TextLayer, Transform2D, MaskConfig } from './types';\n\n/**\n * LayerRenderer - Handles rendering of individual layers\n * Single responsibility: Draw a single layer to the canvas context\n */\nexport class LayerRenderer {\n private ctx: OffscreenCanvasRenderingContext2D;\n private width: number;\n private height: number;\n\n constructor(ctx: OffscreenCanvasRenderingContext2D, width: number, height: number) {\n this.ctx = ctx;\n this.width = width;\n this.height = height;\n this.ensureHighQualityRendering();\n }\n\n private ensureHighQualityRendering(): void {\n this.ctx.imageSmoothingEnabled = true;\n this.ctx.imageSmoothingQuality = 'high';\n }\n\n /**\n * Render a single layer with all its properties\n */\n async renderLayer(layer: Layer): Promise<void> {\n if (!layer.visible || layer.opacity <= 0) return;\n\n this.ctx.save();\n\n try {\n this.ensureHighQualityRendering();\n\n // Apply layer properties\n this.ctx.globalAlpha = layer.opacity;\n\n if (layer.blendMode) {\n this.ctx.globalCompositeOperation = layer.blendMode;\n }\n\n if (layer.transform) {\n this.applyTransform(layer.transform);\n }\n\n // Render based on layer type\n switch (layer.type) {\n case 'video':\n await this.renderVideoLayer(layer as VideoLayer);\n break;\n case 'image':\n await this.renderImageLayer(layer as ImageLayer);\n break;\n case 'text':\n await this.renderTextLayer(layer as TextLayer);\n break;\n }\n\n // Apply mask if present\n if (layer.mask) {\n this.applyMask(layer.mask);\n }\n } finally {\n this.ctx.restore();\n }\n }\n\n private applyTransform(transform: Transform2D): void {\n const centerX = this.width * (transform.anchorX ?? 0.5);\n const centerY = this.height * (transform.anchorY ?? 0.5);\n\n this.ctx.translate(transform.x + centerX, transform.y + centerY);\n\n if (transform.rotation) {\n this.ctx.rotate(transform.rotation);\n }\n\n this.ctx.scale(transform.scaleX, transform.scaleY);\n\n if (transform.skewX || transform.skewY) {\n this.ctx.transform(1, transform.skewY ?? 0, transform.skewX ?? 0, 1, 0, 0);\n }\n\n this.ctx.translate(-centerX, -centerY);\n }\n\n private async renderVideoLayer(layer: VideoLayer): Promise<void> {\n const { videoFrame, crop } = layer;\n\n // Get video dimensions\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n // Calculate scaling to fit (contain mode - preserve aspect ratio)\n const scaleX = this.width / videoWidth;\n const scaleY = this.height / videoHeight;\n\n // Use the smaller scale to ensure entire video fits\n const scale = Math.min(scaleX, scaleY);\n\n // Calculate final render dimensions\n const renderWidth = Math.round(videoWidth * scale);\n const renderHeight = Math.round(videoHeight * scale);\n\n // Center the video\n const renderX = Math.round((this.width - renderWidth) / 2);\n const renderY = Math.round((this.height - renderHeight) / 2);\n\n if (crop) {\n this.ctx.drawImage(\n videoFrame,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n renderX,\n renderY,\n renderWidth,\n renderHeight\n );\n } else {\n this.ctx.drawImage(videoFrame, renderX, renderY, renderWidth, renderHeight);\n }\n }\n\n private async renderImageLayer(layer: ImageLayer): Promise<void> {\n const { source, crop } = layer;\n\n // Handle ImageData by putting it on canvas first\n if (source instanceof ImageData) {\n if (crop) {\n // For ImageData with crop, we need to extract the cropped region\n const tempCanvas = new OffscreenCanvas(crop.width, crop.height);\n const tempCtx = tempCanvas.getContext('2d')!;\n tempCtx.putImageData(source, -crop.x, -crop.y);\n this.ctx.drawImage(tempCanvas, 0, 0, this.width, this.height);\n } else {\n // Put ImageData directly\n this.ctx.putImageData(source, 0, 0);\n }\n } else {\n // ImageBitmap can be drawn directly\n if (!source) {\n return;\n }\n if (crop) {\n this.ctx.drawImage(\n source,\n crop.x,\n crop.y,\n crop.width,\n crop.height,\n 0,\n 0,\n this.width,\n this.height\n );\n } else {\n this.ctx.drawImage(source, 0, 0, this.width, this.height);\n }\n }\n }\n\n private async renderTextLayer(layer: TextLayer): Promise<void> {\n const fontSize = layer.fontSize ?? 16;\n const fontFamily = layer.fontFamily ?? 'sans-serif';\n const fontWeight = layer.fontWeight ?? 'normal';\n const fontStyle = layer.fontStyle ?? 'normal';\n\n this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;\n this.ctx.fillStyle = layer.color ?? '#000000';\n this.ctx.textAlign = layer.textAlign ?? 'left';\n this.ctx.textBaseline = layer.verticalAlign ?? 'top';\n\n // Apply letter spacing if supported\n if (layer.letterSpacing && typeof (this.ctx as any).letterSpacing !== 'undefined') {\n (this.ctx as any).letterSpacing = `${layer.letterSpacing}px`;\n }\n\n // Ensure high-quality text rendering\n this.ensureHighQualityRendering();\n\n // Calculate position with sub-pixel adjustment for sharper rendering\n const baseX = this.calculateTextX(layer.textAlign);\n const baseY = this.calculateTextY(layer.verticalAlign, fontSize);\n const x = Math.round(baseX) + 0.5;\n const y = Math.round(baseY) + 0.5;\n\n // Apply shadow before rendering text (if needed)\n if (layer.shadow) {\n this.ctx.shadowColor = layer.shadow.color;\n this.ctx.shadowOffsetX = layer.shadow.offsetX;\n this.ctx.shadowOffsetY = layer.shadow.offsetY;\n this.ctx.shadowBlur = layer.shadow.blur;\n }\n\n // Render stroke with enhanced quality (multi-layer approach)\n if (layer.strokeColor && layer.strokeWidth && layer.strokeWidth > 0) {\n this.drawEnhancedStroke(layer.text, x, y, layer.strokeColor, layer.strokeWidth);\n }\n\n // Render fill text\n this.ctx.fillText(layer.text, x, y);\n\n // Reset shadow\n if (layer.shadow) {\n this.ctx.shadowColor = 'transparent';\n this.ctx.shadowOffsetX = 0;\n this.ctx.shadowOffsetY = 0;\n this.ctx.shadowBlur = 0;\n }\n }\n\n /**\n * Draw enhanced multi-layer stroke for better text visibility\n */\n private drawEnhancedStroke(\n text: string,\n x: number,\n y: number,\n strokeColor: string,\n strokeWidth: number\n ): void {\n this.ctx.save();\n\n this.ctx.strokeStyle = strokeColor;\n this.ctx.lineJoin = 'round';\n this.ctx.lineCap = 'round';\n this.ctx.miterLimit = 2;\n\n // Multi-layer stroke for enhanced visibility without excessive thickness\n const layers = [1.1, 1.0];\n layers.forEach((multiplier) => {\n this.ctx.lineWidth = strokeWidth * multiplier;\n this.ctx.strokeText(text, x, y);\n });\n\n this.ctx.restore();\n }\n\n private calculateTextX(align?: 'left' | 'center' | 'right'): number {\n switch (align) {\n case 'center':\n return this.width / 2;\n case 'right':\n return this.width;\n default:\n return 0;\n }\n }\n\n private calculateTextY(align?: 'top' | 'middle' | 'bottom', fontSize: number = 16): number {\n switch (align) {\n case 'middle':\n return this.height / 2;\n case 'bottom':\n // Place text at 85% height to avoid bottom edge, similar to SubtitleComposer's 70% position\n return this.height * 0.85;\n default:\n return fontSize;\n }\n }\n\n private applyMask(mask: MaskConfig): void {\n this.ctx.globalCompositeOperation = mask.invert ? 'source-out' : 'destination-in';\n\n if (mask.source) {\n this.ctx.drawImage(mask.source, 0, 0, this.width, this.height);\n } else if (mask.shape === 'circle') {\n this.ctx.beginPath();\n this.ctx.arc(\n this.width / 2,\n this.height / 2,\n Math.min(this.width, this.height) / 2,\n 0,\n Math.PI * 2\n );\n this.ctx.fill();\n }\n }\n\n updateDimensions(width: number, height: number): void {\n this.width = width;\n this.height = height;\n this.ensureHighQualityRendering();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,189 +0,0 @@
1
- class TransitionProcessor {
2
- width;
3
- height;
4
- constructor(width, height) {
5
- this.width = width;
6
- this.height = height;
7
- }
8
- /**
9
- * Apply transition effect to the canvas context
10
- * Returns true if transition was applied, false if not needed
11
- */
12
- applyTransition(ctx, transition) {
13
- if (!transition || transition.progress <= 0) return false;
14
- const progress = this.calculateEasedProgress(transition.progress, transition.easing);
15
- switch (transition.type) {
16
- case "fade":
17
- return this.applyFade(ctx, progress);
18
- case "slide":
19
- return this.applySlide(ctx, progress, transition.direction);
20
- case "wipe":
21
- return this.applyWipe(ctx, progress, transition.direction);
22
- case "zoom":
23
- return this.applyZoom(ctx, progress, transition.direction);
24
- case "rotate":
25
- return this.applyRotate(ctx, progress);
26
- case "dissolve":
27
- return this.applyDissolve(ctx, progress);
28
- default:
29
- return false;
30
- }
31
- }
32
- calculateEasedProgress(progress, easing) {
33
- switch (easing) {
34
- case "ease-in":
35
- return progress * progress;
36
- case "ease-out":
37
- return 1 - (1 - progress) * (1 - progress);
38
- case "ease-in-out":
39
- return progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;
40
- default:
41
- return progress;
42
- }
43
- }
44
- applyFade(ctx, progress) {
45
- ctx.globalAlpha = progress;
46
- return true;
47
- }
48
- applySlide(ctx, progress, direction) {
49
- const distance = 1 - progress;
50
- switch (direction) {
51
- case "left":
52
- ctx.translate(-this.width * distance, 0);
53
- break;
54
- case "right":
55
- ctx.translate(this.width * distance, 0);
56
- break;
57
- case "up":
58
- ctx.translate(0, -this.height * distance);
59
- break;
60
- case "down":
61
- ctx.translate(0, this.height * distance);
62
- break;
63
- default:
64
- ctx.translate(-this.width * distance, 0);
65
- }
66
- return true;
67
- }
68
- applyWipe(ctx, progress, direction) {
69
- ctx.save();
70
- ctx.beginPath();
71
- switch (direction) {
72
- case "left":
73
- ctx.rect(0, 0, this.width * progress, this.height);
74
- break;
75
- case "right":
76
- ctx.rect(this.width * (1 - progress), 0, this.width * progress, this.height);
77
- break;
78
- case "up":
79
- ctx.rect(0, 0, this.width, this.height * progress);
80
- break;
81
- case "down":
82
- ctx.rect(0, this.height * (1 - progress), this.width, this.height * progress);
83
- break;
84
- default:
85
- ctx.rect(0, 0, this.width * progress, this.height);
86
- }
87
- ctx.clip();
88
- return true;
89
- }
90
- applyZoom(ctx, progress, direction) {
91
- const scale = direction === "out" ? 1 + (1 - progress) : progress;
92
- const centerX = this.width / 2;
93
- const centerY = this.height / 2;
94
- ctx.translate(centerX, centerY);
95
- ctx.scale(scale, scale);
96
- ctx.translate(-centerX, -centerY);
97
- if (direction === "out") {
98
- ctx.globalAlpha = progress;
99
- }
100
- return true;
101
- }
102
- applyRotate(ctx, progress) {
103
- const rotation = (1 - progress) * Math.PI * 2;
104
- const centerX = this.width / 2;
105
- const centerY = this.height / 2;
106
- ctx.translate(centerX, centerY);
107
- ctx.rotate(rotation);
108
- ctx.translate(-centerX, -centerY);
109
- return true;
110
- }
111
- applyDissolve(ctx, progress) {
112
- ctx.globalAlpha = progress;
113
- ctx.globalCompositeOperation = "multiply";
114
- return true;
115
- }
116
- /**
117
- * Create a transition mask for advanced effects
118
- */
119
- createTransitionMask(transition, canvas) {
120
- const ctx = canvas.getContext("2d");
121
- if (!ctx) return null;
122
- const imageData = ctx.createImageData(this.width, this.height);
123
- const data = imageData.data;
124
- const progress = this.calculateEasedProgress(transition.progress, transition.easing);
125
- for (let y = 0; y < this.height; y++) {
126
- for (let x = 0; x < this.width; x++) {
127
- const index = (y * this.width + x) * 4;
128
- let alpha = 255;
129
- switch (transition.type) {
130
- case "wipe":
131
- alpha = this.calculateWipeAlpha(x, y, progress, transition.direction);
132
- break;
133
- case "dissolve":
134
- alpha = Math.random() < progress ? 255 : 0;
135
- break;
136
- default:
137
- alpha = Math.floor(255 * progress);
138
- }
139
- data[index] = 255;
140
- data[index + 1] = 255;
141
- data[index + 2] = 255;
142
- data[index + 3] = alpha;
143
- }
144
- }
145
- return imageData;
146
- }
147
- calculateWipeAlpha(x, y, progress, direction) {
148
- let position = 0;
149
- switch (direction) {
150
- case "left":
151
- position = x / this.width;
152
- break;
153
- case "right":
154
- position = 1 - x / this.width;
155
- break;
156
- case "up":
157
- position = y / this.height;
158
- break;
159
- case "down":
160
- position = 1 - y / this.height;
161
- break;
162
- case "in": {
163
- const cx = x - this.width / 2;
164
- const cy = y - this.height / 2;
165
- const maxDist = Math.sqrt(this.width * this.width + this.height * this.height) / 2;
166
- position = Math.sqrt(cx * cx + cy * cy) / maxDist;
167
- break;
168
- }
169
- case "out": {
170
- const cx2 = x - this.width / 2;
171
- const cy2 = y - this.height / 2;
172
- const maxDist2 = Math.sqrt(this.width * this.width + this.height * this.height) / 2;
173
- position = 1 - Math.sqrt(cx2 * cx2 + cy2 * cy2) / maxDist2;
174
- break;
175
- }
176
- default:
177
- position = x / this.width;
178
- }
179
- return position < progress ? 255 : 0;
180
- }
181
- updateDimensions(width, height) {
182
- this.width = width;
183
- this.height = height;
184
- }
185
- }
186
- export {
187
- TransitionProcessor
188
- };
189
- //# sourceMappingURL=TransitionProcessor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TransitionProcessor.js","sources":["../../../src/stages/compose/TransitionProcessor.ts"],"sourcesContent":["import type { TransitionEffect } from './types';\n\n/**\n * TransitionProcessor - Handles transition effects between frames/scenes\n * Single responsibility: Apply transition transformations to canvas context\n */\nexport class TransitionProcessor {\n private width: number;\n private height: number;\n\n constructor(width: number, height: number) {\n this.width = width;\n this.height = height;\n }\n\n /**\n * Apply transition effect to the canvas context\n * Returns true if transition was applied, false if not needed\n */\n applyTransition(ctx: OffscreenCanvasRenderingContext2D, transition: TransitionEffect): boolean {\n if (!transition || transition.progress <= 0) return false;\n\n const progress = this.calculateEasedProgress(transition.progress, transition.easing);\n\n switch (transition.type) {\n case 'fade':\n return this.applyFade(ctx, progress);\n case 'slide':\n return this.applySlide(ctx, progress, transition.direction);\n case 'wipe':\n return this.applyWipe(ctx, progress, transition.direction);\n case 'zoom':\n return this.applyZoom(ctx, progress, transition.direction);\n case 'rotate':\n return this.applyRotate(ctx, progress);\n case 'dissolve':\n return this.applyDissolve(ctx, progress);\n default:\n return false;\n }\n }\n\n private calculateEasedProgress(\n progress: number,\n easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out'\n ): number {\n switch (easing) {\n case 'ease-in':\n return progress * progress;\n case 'ease-out':\n return 1 - (1 - progress) * (1 - progress);\n case 'ease-in-out':\n return progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;\n default:\n return progress;\n }\n }\n\n private applyFade(ctx: OffscreenCanvasRenderingContext2D, progress: number): boolean {\n ctx.globalAlpha = progress;\n return true;\n }\n\n private applySlide(\n ctx: OffscreenCanvasRenderingContext2D,\n progress: number,\n direction?: 'left' | 'right' | 'up' | 'down' | 'in' | 'out'\n ): boolean {\n const distance = 1 - progress;\n\n switch (direction) {\n case 'left':\n ctx.translate(-this.width * distance, 0);\n break;\n case 'right':\n ctx.translate(this.width * distance, 0);\n break;\n case 'up':\n ctx.translate(0, -this.height * distance);\n break;\n case 'down':\n ctx.translate(0, this.height * distance);\n break;\n default:\n ctx.translate(-this.width * distance, 0);\n }\n\n return true;\n }\n\n private applyWipe(\n ctx: OffscreenCanvasRenderingContext2D,\n progress: number,\n direction?: 'left' | 'right' | 'up' | 'down' | 'in' | 'out'\n ): boolean {\n ctx.save();\n ctx.beginPath();\n\n switch (direction) {\n case 'left':\n ctx.rect(0, 0, this.width * progress, this.height);\n break;\n case 'right':\n ctx.rect(this.width * (1 - progress), 0, this.width * progress, this.height);\n break;\n case 'up':\n ctx.rect(0, 0, this.width, this.height * progress);\n break;\n case 'down':\n ctx.rect(0, this.height * (1 - progress), this.width, this.height * progress);\n break;\n default:\n ctx.rect(0, 0, this.width * progress, this.height);\n }\n\n ctx.clip();\n return true;\n }\n\n private applyZoom(\n ctx: OffscreenCanvasRenderingContext2D,\n progress: number,\n direction?: 'left' | 'right' | 'up' | 'down' | 'in' | 'out'\n ): boolean {\n const scale = direction === 'out' ? 1 + (1 - progress) : progress;\n const centerX = this.width / 2;\n const centerY = this.height / 2;\n\n ctx.translate(centerX, centerY);\n ctx.scale(scale, scale);\n ctx.translate(-centerX, -centerY);\n\n if (direction === 'out') {\n ctx.globalAlpha = progress;\n }\n\n return true;\n }\n\n private applyRotate(ctx: OffscreenCanvasRenderingContext2D, progress: number): boolean {\n const rotation = (1 - progress) * Math.PI * 2;\n const centerX = this.width / 2;\n const centerY = this.height / 2;\n\n ctx.translate(centerX, centerY);\n ctx.rotate(rotation);\n ctx.translate(-centerX, -centerY);\n\n return true;\n }\n\n private applyDissolve(ctx: OffscreenCanvasRenderingContext2D, progress: number): boolean {\n // Simple dissolve using alpha\n ctx.globalAlpha = progress;\n ctx.globalCompositeOperation = 'multiply';\n return true;\n }\n\n /**\n * Create a transition mask for advanced effects\n */\n createTransitionMask(transition: TransitionEffect, canvas: OffscreenCanvas): ImageData | null {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n\n const imageData = ctx.createImageData(this.width, this.height);\n const data = imageData.data;\n const progress = this.calculateEasedProgress(transition.progress, transition.easing);\n\n // Generate gradient mask based on transition type\n for (let y = 0; y < this.height; y++) {\n for (let x = 0; x < this.width; x++) {\n const index = (y * this.width + x) * 4;\n let alpha = 255;\n\n switch (transition.type) {\n case 'wipe':\n alpha = this.calculateWipeAlpha(x, y, progress, transition.direction);\n break;\n case 'dissolve':\n // Random noise dissolve\n alpha = Math.random() < progress ? 255 : 0;\n break;\n default:\n alpha = Math.floor(255 * progress);\n }\n\n data[index] = 255; // R\n data[index + 1] = 255; // G\n data[index + 2] = 255; // B\n data[index + 3] = alpha; // A\n }\n }\n\n return imageData;\n }\n\n private calculateWipeAlpha(\n x: number,\n y: number,\n progress: number,\n direction?: 'left' | 'right' | 'up' | 'down' | 'in' | 'out'\n ): number {\n let position = 0;\n\n switch (direction) {\n case 'left':\n position = x / this.width;\n break;\n case 'right':\n position = 1 - x / this.width;\n break;\n case 'up':\n position = y / this.height;\n break;\n case 'down':\n position = 1 - y / this.height;\n break;\n case 'in': {\n // Radial wipe from center\n const cx = x - this.width / 2;\n const cy = y - this.height / 2;\n const maxDist = Math.sqrt(this.width * this.width + this.height * this.height) / 2;\n position = Math.sqrt(cx * cx + cy * cy) / maxDist;\n break;\n }\n case 'out': {\n // Radial wipe to center\n const cx2 = x - this.width / 2;\n const cy2 = y - this.height / 2;\n const maxDist2 = Math.sqrt(this.width * this.width + this.height * this.height) / 2;\n position = 1 - Math.sqrt(cx2 * cx2 + cy2 * cy2) / maxDist2;\n break;\n }\n default:\n position = x / this.width;\n }\n\n return position < progress ? 255 : 0;\n }\n\n updateDimensions(width: number, height: number): void {\n this.width = width;\n this.height = height;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,186 +0,0 @@
1
- import { frameDurationFromFps } from "../../utils/time-utils.js";
2
- import { LayerRenderer } from "./LayerRenderer.js";
3
- import { TransitionProcessor } from "./TransitionProcessor.js";
4
- import { FilterProcessor } from "./FilterProcessor.js";
5
- class VideoComposer {
6
- config;
7
- canvas;
8
- ctx;
9
- layerRenderer;
10
- transitionProcessor;
11
- filterProcessor;
12
- timelineContext;
13
- constructor(config) {
14
- this.config = this.applyDefaults(config);
15
- this.canvas = new OffscreenCanvas(this.config.width, this.config.height);
16
- const ctx = this.canvas.getContext("2d", {
17
- alpha: true,
18
- desynchronized: true,
19
- willReadFrequently: false,
20
- colorSpace: "srgb"
21
- });
22
- if (!ctx) {
23
- throw new Error("Failed to create 2D rendering context");
24
- }
25
- this.ctx = ctx;
26
- this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
27
- this.ctx.imageSmoothingQuality = "high";
28
- this.layerRenderer = new LayerRenderer(ctx, this.config.width, this.config.height);
29
- this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);
30
- this.filterProcessor = new FilterProcessor();
31
- this.timelineContext = this.config.timeline;
32
- }
33
- applyDefaults(config) {
34
- return {
35
- width: config.width || 720,
36
- height: config.height || 1280,
37
- fps: config.fps || 30,
38
- backgroundColor: config.backgroundColor ?? "#000",
39
- renderer: config.renderer ?? "canvas2d",
40
- enableSmoothing: config.enableSmoothing ?? true,
41
- enableHardwareAcceleration: config.enableHardwareAcceleration ?? true,
42
- revision: config.revision ?? 0,
43
- inputHighWaterMark: config.inputHighWaterMark ?? 3,
44
- outputHighWaterMark: config.outputHighWaterMark ?? 1,
45
- maxLayers: config.maxLayers ?? 100,
46
- timeline: config.timeline ?? {
47
- clipId: "default",
48
- trackId: "main",
49
- clipStartUs: 0,
50
- clipDurationUs: Infinity,
51
- compositionFps: 30
52
- }
53
- };
54
- }
55
- createStreams(_instruction) {
56
- if (_instruction?.baseConfig.timeline) {
57
- this.timelineContext = _instruction.baseConfig.timeline;
58
- }
59
- const stream = new TransformStream(
60
- {
61
- transform: async (request, controller) => {
62
- const result = await this.composeFrame(request);
63
- controller.enqueue({
64
- frame: result.frame,
65
- metadata: result.metadata
66
- });
67
- },
68
- flush: async () => {
69
- this.filterProcessor.clearCache();
70
- }
71
- },
72
- {
73
- highWaterMark: this.config.inputHighWaterMark
74
- },
75
- {
76
- highWaterMark: this.config.outputHighWaterMark
77
- }
78
- );
79
- return {
80
- composeStream: stream.writable,
81
- cacheStream: stream.readable
82
- };
83
- }
84
- async composeFrame(request) {
85
- if (request.layers.length > this.config.maxLayers) {
86
- throw new Error(`Too many layers: ${request.layers.length} > ${this.config.maxLayers}`);
87
- }
88
- this.clearCanvas();
89
- if (request.transition) {
90
- this.ctx.save();
91
- this.transitionProcessor.applyTransition(this.ctx, request.transition);
92
- }
93
- for (const layer of request.layers) {
94
- if (!layer.visible || layer.opacity <= 0) {
95
- if (layer.type === "video") {
96
- const vf = layer.videoFrame;
97
- vf?.close?.();
98
- }
99
- continue;
100
- }
101
- try {
102
- if (layer.filters && layer.filters.length > 0) {
103
- this.ctx.save();
104
- this.filterProcessor.applyFilters(this.ctx, layer.filters);
105
- }
106
- await this.layerRenderer.renderLayer(layer);
107
- if (layer.filters && layer.filters.length > 0) {
108
- this.ctx.restore();
109
- }
110
- } finally {
111
- if (layer.type === "video") {
112
- const vf = layer.videoFrame;
113
- vf?.close?.();
114
- }
115
- }
116
- }
117
- if (request.transition) {
118
- this.ctx.restore();
119
- }
120
- const frame = await this.createOutputFrame(request.timeUs);
121
- const gopSerial = request.gopSerial;
122
- const isKeyframe = request.isKeyframe;
123
- return {
124
- frame,
125
- timeUs: request.timeUs,
126
- metadata: {
127
- layerCount: request.layers.length,
128
- renderTime: 0,
129
- gpuAccelerated: this.config.enableHardwareAcceleration && this.config.renderer !== "canvas2d",
130
- ...typeof gopSerial === "number" && { gopSerial },
131
- ...typeof isKeyframe === "boolean" && { isKeyframe }
132
- }
133
- };
134
- }
135
- async composeTransition(fromRequest, toRequest, transition) {
136
- await this.composeFrame(fromRequest);
137
- const toFrameRequest = {
138
- ...toRequest,
139
- transition
140
- };
141
- return this.composeFrame(toFrameRequest);
142
- }
143
- clearCanvas() {
144
- if (this.config.backgroundColor) {
145
- this.ctx.fillStyle = this.config.backgroundColor;
146
- this.ctx.fillRect(0, 0, this.config.width, this.config.height);
147
- } else {
148
- this.ctx.clearRect(0, 0, this.config.width, this.config.height);
149
- }
150
- }
151
- // private sortLayers(layers: Layer[]): Layer[] {
152
- // return [...layers].sort((a, b) => a.zIndex - b.zIndex);
153
- // }
154
- async createOutputFrame(timeUs) {
155
- const duration = frameDurationFromFps(this.timelineContext.compositionFps);
156
- const frame = new VideoFrame(this.canvas, {
157
- timestamp: timeUs,
158
- duration,
159
- alpha: "discard",
160
- visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height }
161
- });
162
- return frame;
163
- }
164
- updateConfig(config) {
165
- Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));
166
- if (config.width || config.height) {
167
- this.canvas.width = this.config.width;
168
- this.canvas.height = this.config.height;
169
- this.layerRenderer.updateDimensions(this.config.width, this.config.height);
170
- this.transitionProcessor.updateDimensions(this.config.width, this.config.height);
171
- }
172
- if (config.enableSmoothing !== void 0) {
173
- this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
174
- }
175
- if (config.timeline) {
176
- this.timelineContext = config.timeline;
177
- }
178
- }
179
- dispose() {
180
- this.filterProcessor.clearCache();
181
- }
182
- }
183
- export {
184
- VideoComposer
185
- };
186
- //# sourceMappingURL=VideoComposer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoComposer.js","sources":["../../../src/stages/compose/VideoComposer.ts"],"sourcesContent":["import type {\n VideoComposeConfig,\n ComposeRequest,\n ComposeResult,\n TransitionEffect,\n VideoLayer,\n ComposeTimelineContext,\n} from './types';\nimport { frameDurationFromFps } from '../../utils/time-utils';\nimport { LayerRenderer } from './LayerRenderer';\nimport { TransitionProcessor } from './TransitionProcessor';\nimport { FilterProcessor } from './FilterProcessor';\nimport { ClipInstructionSet } from './instructions';\n\ninterface ComposeStreams {\n composeStream: WritableStream<ComposeRequest>;\n cacheStream: ReadableStream<VideoFrame>;\n}\n\n/**\n * VideoComposer - Main visual composition orchestrator\n */\nexport class VideoComposer {\n readonly config: Required<VideoComposeConfig>;\n readonly canvas: OffscreenCanvas;\n\n private ctx: OffscreenCanvasRenderingContext2D;\n private layerRenderer: LayerRenderer;\n private transitionProcessor: TransitionProcessor;\n private filterProcessor: FilterProcessor;\n private timelineContext: ComposeTimelineContext;\n\n constructor(config: VideoComposeConfig) {\n this.config = this.applyDefaults(config);\n this.canvas = new OffscreenCanvas(this.config.width, this.config.height);\n\n const ctx = this.canvas.getContext('2d', {\n alpha: true,\n desynchronized: true,\n willReadFrequently: false,\n colorSpace: 'srgb',\n });\n\n if (!ctx) {\n throw new Error('Failed to create 2D rendering context');\n }\n\n this.ctx = ctx;\n this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;\n this.ctx.imageSmoothingQuality = 'high';\n\n this.layerRenderer = new LayerRenderer(ctx, this.config.width, this.config.height);\n this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);\n this.filterProcessor = new FilterProcessor();\n this.timelineContext = this.config.timeline;\n }\n\n private applyDefaults(config: VideoComposeConfig): Required<VideoComposeConfig> {\n return {\n width: config.width || 720,\n height: config.height || 1280,\n fps: config.fps || 30,\n backgroundColor: config.backgroundColor ?? '#000',\n renderer: config.renderer ?? 'canvas2d',\n enableSmoothing: config.enableSmoothing ?? true,\n enableHardwareAcceleration: config.enableHardwareAcceleration ?? true,\n revision: config.revision ?? 0,\n inputHighWaterMark: config.inputHighWaterMark ?? 3,\n outputHighWaterMark: config.outputHighWaterMark ?? 1,\n maxLayers: config.maxLayers ?? 100,\n timeline: config.timeline ?? {\n clipId: 'default',\n trackId: 'main',\n clipStartUs: 0,\n clipDurationUs: Infinity,\n compositionFps: 30,\n },\n };\n }\n\n createStreams(_instruction?: ClipInstructionSet): ComposeStreams {\n if (_instruction?.baseConfig.timeline) {\n this.timelineContext = _instruction.baseConfig.timeline;\n }\n\n // Always create new streams for each clip\n // ReadableStreams can only be consumed once\n const stream = new TransformStream<ComposeRequest, any>(\n {\n transform: async (request, controller) => {\n // console.log('[VideoComposer] transform', request, controller.desiredSize);\n const result = await this.composeFrame(request);\n controller.enqueue({\n frame: result.frame,\n metadata: result.metadata,\n });\n },\n\n flush: async () => {\n this.filterProcessor.clearCache();\n },\n },\n {\n highWaterMark: this.config.inputHighWaterMark,\n },\n {\n highWaterMark: this.config.outputHighWaterMark,\n }\n );\n // const [encodeStream, cacheStream] = stream.readable.tee();\n return {\n composeStream: stream.writable,\n cacheStream: stream.readable,\n };\n }\n\n async composeFrame(request: ComposeRequest): Promise<ComposeResult> {\n if (request.layers.length > this.config.maxLayers) {\n throw new Error(`Too many layers: ${request.layers.length} > ${this.config.maxLayers}`);\n }\n\n this.clearCanvas();\n // const sortedLayers = this.sortLayers(request.layers);\n\n if (request.transition) {\n this.ctx.save();\n this.transitionProcessor.applyTransition(this.ctx, request.transition);\n }\n\n for (const layer of request.layers) {\n if (!layer.visible || layer.opacity <= 0) {\n // Close video frame for invisible layers\n if ((layer as any).type === 'video') {\n const vf = (layer as VideoLayer).videoFrame;\n vf?.close?.();\n }\n continue;\n }\n\n try {\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.save();\n this.filterProcessor.applyFilters(this.ctx, layer.filters);\n }\n\n await this.layerRenderer.renderLayer(layer);\n\n if (layer.filters && layer.filters.length > 0) {\n this.ctx.restore();\n }\n } finally {\n // Always close video frame after rendering (or on error)\n if ((layer as any).type === 'video') {\n const vf = (layer as VideoLayer).videoFrame;\n vf?.close?.();\n }\n }\n }\n\n if (request.transition) {\n this.ctx.restore();\n }\n\n const frame = await this.createOutputFrame(request.timeUs);\n\n const gopSerial = (request as any).gopSerial;\n const isKeyframe = (request as any).isKeyframe;\n\n return {\n frame,\n timeUs: request.timeUs,\n metadata: {\n layerCount: request.layers.length,\n renderTime: 0,\n gpuAccelerated:\n this.config.enableHardwareAcceleration && this.config.renderer !== 'canvas2d',\n ...(typeof gopSerial === 'number' && { gopSerial }),\n ...(typeof isKeyframe === 'boolean' && { isKeyframe }),\n },\n };\n }\n\n async composeTransition(\n fromRequest: ComposeRequest,\n toRequest: ComposeRequest,\n transition: TransitionEffect\n ): Promise<ComposeResult> {\n await this.composeFrame(fromRequest);\n\n const toFrameRequest = {\n ...toRequest,\n transition,\n };\n\n return this.composeFrame(toFrameRequest);\n }\n\n private clearCanvas(): void {\n if (this.config.backgroundColor) {\n this.ctx.fillStyle = this.config.backgroundColor;\n this.ctx.fillRect(0, 0, this.config.width, this.config.height);\n } else {\n this.ctx.clearRect(0, 0, this.config.width, this.config.height);\n }\n }\n\n // private sortLayers(layers: Layer[]): Layer[] {\n // return [...layers].sort((a, b) => a.zIndex - b.zIndex);\n // }\n\n private async createOutputFrame(timeUs: number): Promise<VideoFrame> {\n const duration = frameDurationFromFps(this.timelineContext.compositionFps);\n const frame = new VideoFrame(this.canvas, {\n timestamp: timeUs,\n duration,\n alpha: 'discard',\n visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height },\n });\n return frame;\n }\n\n updateConfig(config: Partial<VideoComposeConfig>): void {\n Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));\n\n if (config.width || config.height) {\n this.canvas.width = this.config.width;\n this.canvas.height = this.config.height;\n this.layerRenderer.updateDimensions(this.config.width, this.config.height);\n this.transitionProcessor.updateDimensions(this.config.width, this.config.height);\n }\n\n if (config.enableSmoothing !== undefined) {\n this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;\n }\n\n if (config.timeline) {\n this.timelineContext = config.timeline;\n }\n }\n\n dispose(): void {\n this.filterProcessor.clearCache();\n }\n}\n"],"names":[],"mappings":"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"}