@meframe/core 0.1.3 → 0.1.5
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/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +7 -2
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +17 -8
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +9 -2
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/LayerRenderer.js +20 -5
- package/dist/stages/compose/LayerRenderer.js.map +1 -1
- package/dist/utils/object-utils.d.ts +14 -0
- package/dist/utils/object-utils.d.ts.map +1 -1
- package/dist/utils/object-utils.js +27 -1
- package/dist/utils/object-utils.js.map +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.C8728Oi3.js → video-compose.worker.CQwmNfXT.js} +61 -11
- package/dist/workers/stages/compose/video-compose.worker.CQwmNfXT.js.map +1 -0
- package/dist/workers/worker-manifest.json +1 -1
- package/package.json +1 -1
- package/dist/workers/stages/compose/video-compose.worker.C8728Oi3.js.map +0 -1
|
@@ -106,14 +106,22 @@ class LayerRenderer {
|
|
|
106
106
|
if (imageLayer.source) {
|
|
107
107
|
const imgWidth = imageLayer.source.width;
|
|
108
108
|
const imgHeight = imageLayer.source.height;
|
|
109
|
-
|
|
109
|
+
const hasValidRenderConfig = !!(imageLayer.renderConfig?.width !== void 0 || imageLayer.renderConfig?.height !== void 0);
|
|
110
|
+
if (hasValidRenderConfig) {
|
|
111
|
+
return this.calculateDimensionsFromConfig(imgWidth, imgHeight, imageLayer.renderConfig);
|
|
112
|
+
}
|
|
113
|
+
return { width: imgWidth, height: imgHeight };
|
|
110
114
|
}
|
|
111
115
|
} else if (layer.type === "video") {
|
|
112
116
|
const videoLayer = layer;
|
|
113
117
|
const videoFrame = videoLayer.videoFrame;
|
|
114
118
|
const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;
|
|
115
119
|
const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;
|
|
116
|
-
|
|
120
|
+
const hasValidRenderConfig = !!(videoLayer.renderConfig?.width !== void 0 || videoLayer.renderConfig?.height !== void 0);
|
|
121
|
+
if (hasValidRenderConfig) {
|
|
122
|
+
return this.calculateDimensionsFromConfig(videoWidth, videoHeight, videoLayer.renderConfig);
|
|
123
|
+
}
|
|
124
|
+
return { width: videoWidth, height: videoHeight };
|
|
117
125
|
}
|
|
118
126
|
return { width: this.width, height: this.height };
|
|
119
127
|
}
|
|
@@ -165,7 +173,8 @@ class LayerRenderer {
|
|
|
165
173
|
let renderY;
|
|
166
174
|
let renderWidth;
|
|
167
175
|
let renderHeight;
|
|
168
|
-
|
|
176
|
+
const hasValidRenderConfig = !!(renderConfig?.width !== void 0 || renderConfig?.height !== void 0);
|
|
177
|
+
if (hasValidRenderConfig) {
|
|
169
178
|
const dimensions = this.calculateDimensionsFromConfig(videoWidth, videoHeight, renderConfig);
|
|
170
179
|
renderWidth = dimensions.width;
|
|
171
180
|
renderHeight = dimensions.height;
|
|
@@ -196,7 +205,7 @@ class LayerRenderer {
|
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
renderImageLayer(layer) {
|
|
199
|
-
const { source, crop, renderConfig } = layer;
|
|
208
|
+
const { source, crop, renderConfig, attachmentId } = layer;
|
|
200
209
|
if (source instanceof ImageData) {
|
|
201
210
|
if (crop) {
|
|
202
211
|
const tempCanvas = new OffscreenCanvas(crop.width, crop.height);
|
|
@@ -215,12 +224,18 @@ class LayerRenderer {
|
|
|
215
224
|
let renderY;
|
|
216
225
|
let renderWidth;
|
|
217
226
|
let renderHeight;
|
|
218
|
-
|
|
227
|
+
const hasValidRenderConfig = !!(renderConfig?.width !== void 0 || renderConfig?.height !== void 0);
|
|
228
|
+
if (hasValidRenderConfig) {
|
|
219
229
|
const dimensions = this.calculateDimensionsFromConfig(imgWidth, imgHeight, renderConfig);
|
|
220
230
|
renderWidth = dimensions.width;
|
|
221
231
|
renderHeight = dimensions.height;
|
|
222
232
|
renderX = 0;
|
|
223
233
|
renderY = 0;
|
|
234
|
+
} else if (attachmentId) {
|
|
235
|
+
renderWidth = imgWidth;
|
|
236
|
+
renderHeight = imgHeight;
|
|
237
|
+
renderX = 0;
|
|
238
|
+
renderY = 0;
|
|
224
239
|
} else {
|
|
225
240
|
const naturalScale = this.width / imgWidth;
|
|
226
241
|
const dimensions = this.calculateRenderDimensions(imgWidth, imgHeight, naturalScale);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LayerRenderer.js","sources":["../../../src/stages/compose/LayerRenderer.ts"],"sourcesContent":["import type { Layer, VideoLayer, ImageLayer, TextLayer, Transform2D, MaskConfig } from './types';\nimport { renderBasicText, renderTextWithEntrance } from './text-renderers/basic-text-renderer';\nimport { renderWordByWord } from './text-renderers/word-by-word-renderer';\nimport { renderCharacterKTV } from './text-renderers/character-ktv-renderer';\nimport { renderWordByWordFancy } from './text-renderers/word-fancy-renderer';\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 | CanvasRenderingContext2D;\n private width: number;\n private height: number;\n private currentFrame: number = 0;\n private fps: number = 30;\n private readonly FILL_THRESHOLD = 0.9;\n\n constructor(\n ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,\n width: number,\n height: number,\n fps: number = 30\n ) {\n this.ctx = ctx;\n this.width = width;\n this.height = height;\n this.fps = fps;\n this.ensureHighQualityRendering();\n }\n\n setCurrentFrame(frame: number): void {\n this.currentFrame = frame;\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 renderLayer(layer: Layer): void {\n if (!layer.visible || layer.opacity <= 0) return;\n\n // Only save/restore context if layer has properties that need it\n const needsStateManagement =\n layer.opacity !== 1 || layer.blendMode || layer.transform || layer.mask;\n\n if (needsStateManagement) {\n this.ctx.save();\n }\n\n try {\n // Apply layer properties only when needed\n if (layer.opacity !== 1) {\n this.ctx.globalAlpha = layer.opacity;\n }\n\n if (layer.blendMode) {\n this.ctx.globalCompositeOperation = layer.blendMode;\n }\n\n if (layer.transform) {\n // Get layer dimensions for transform anchor calculation\n const layerDimensions = this.getLayerDimensions(layer);\n this.applyTransform(layer.transform, layerDimensions);\n }\n // Render based on layer type\n switch (layer.type) {\n case 'video':\n this.renderVideoLayer(layer as VideoLayer);\n break;\n case 'image':\n this.renderImageLayer(layer as ImageLayer);\n break;\n case 'text':\n 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 if (needsStateManagement) {\n this.ctx.restore();\n }\n }\n }\n\n private parseDimension(\n value: number | string | undefined,\n canvasSize: number\n ): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'number') return value;\n\n // value is string at this point\n const strValue = value as string;\n\n // Parse percentage string like \"5%\"\n if (strValue.includes('%')) {\n const numValue = parseFloat(strValue);\n return isNaN(numValue) ? undefined : (numValue / 100) * canvasSize;\n }\n\n // Parse as pixel value\n const parsed = parseFloat(strValue);\n return isNaN(parsed) ? undefined : parsed;\n }\n\n /**\n * Calculate dimensions from renderConfig\n * Returns dimensions maintaining aspect ratio when only one dimension is specified\n */\n private calculateDimensionsFromConfig(\n sourceWidth: number,\n sourceHeight: number,\n renderConfig: { width?: number | string; height?: number | string } | undefined\n ): { width: number; height: number } {\n if (!renderConfig) {\n return { width: sourceWidth, height: sourceHeight };\n }\n\n const width = this.parseDimension(renderConfig.width, this.width);\n const height = this.parseDimension(renderConfig.height, this.height);\n\n if (width && height) {\n return { width, height };\n } else if (width) {\n return {\n width,\n height: Math.round((sourceHeight / sourceWidth) * width),\n };\n } else if (height) {\n return {\n width: Math.round((sourceWidth / sourceHeight) * height),\n height,\n };\n } else {\n // renderConfig exists but empty, use original size\n return { width: sourceWidth, height: sourceHeight };\n }\n }\n\n private getLayerDimensions(layer: Layer): { width: number; height: number } {\n if (layer.type === 'image') {\n const imageLayer = layer as ImageLayer;\n if (imageLayer.source) {\n const imgWidth = imageLayer.source.width;\n const imgHeight = imageLayer.source.height;\n return this.calculateDimensionsFromConfig(imgWidth, imgHeight, imageLayer.renderConfig);\n }\n } else if (layer.type === 'video') {\n const videoLayer = layer as VideoLayer;\n const videoFrame = videoLayer.videoFrame;\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n return this.calculateDimensionsFromConfig(videoWidth, videoHeight, videoLayer.renderConfig);\n }\n // Default to canvas dimensions\n return { width: this.width, height: this.height };\n }\n\n /**\n * Calculate render dimensions with smart fill logic\n * @param sourceWidth Source width\n * @param sourceHeight Source height\n * @param naturalScale Natural scale factor (scaleY for video, scaleX for image)\n * @returns Render dimensions and position\n */\n private calculateRenderDimensions(\n sourceWidth: number,\n sourceHeight: number,\n naturalScale: number\n ): { width: number; height: number; x: number; y: number } {\n const scaledWidth = sourceWidth * naturalScale;\n const scaledHeight = sourceHeight * naturalScale;\n\n // Smart fill: when scaled size is close to container (>90%), use cover mode\n const shouldFill =\n scaledWidth / this.width > this.FILL_THRESHOLD &&\n scaledHeight / this.height > this.FILL_THRESHOLD;\n\n let renderWidth: number;\n let renderHeight: number;\n\n if (shouldFill) {\n // Cover mode: use Math.max to ensure entire canvas is covered while maintaining aspect ratio\n const coverScale = Math.max(this.width / sourceWidth, this.height / sourceHeight);\n renderWidth = Math.round(sourceWidth * coverScale);\n renderHeight = Math.round(sourceHeight * coverScale);\n } else {\n // Natural scale mode: use scaled dimensions\n renderWidth = Math.round(scaledWidth);\n renderHeight = Math.round(scaledHeight);\n }\n\n // Center the content\n const renderX = Math.round((this.width - renderWidth) / 2);\n const renderY = Math.round((this.height - renderHeight) / 2);\n\n return { width: renderWidth, height: renderHeight, x: renderX, y: renderY };\n }\n\n private applyTransform(\n transform: Transform2D,\n layerDimensions: { width: number; height: number }\n ): void {\n // Use layer dimensions (not canvas dimensions) for anchor calculation\n const anchorX = transform.anchorX ?? 0.5;\n const anchorY = transform.anchorY ?? 0.5;\n const centerX = layerDimensions.width * anchorX;\n const centerY = layerDimensions.height * anchorY;\n\n // Move to the layer position + anchor offset\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 // Move back by anchor offset\n this.ctx.translate(-centerX, -centerY);\n }\n\n private renderVideoLayer(layer: VideoLayer): void {\n const { videoFrame, crop, renderConfig } = layer;\n\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n let renderX: number;\n let renderY: number;\n let renderWidth: number;\n let renderHeight: number;\n\n if (renderConfig) {\n // Has renderConfig: explicit dimensions\n const dimensions = this.calculateDimensionsFromConfig(videoWidth, videoHeight, renderConfig);\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n // Center the video\n renderX = Math.round((this.width - renderWidth) / 2);\n renderY = Math.round((this.height - renderHeight) / 2);\n } else {\n // No renderConfig: legacy smart fill (height-based)\n const naturalScale = this.height / videoHeight;\n const dimensions = this.calculateRenderDimensions(videoWidth, videoHeight, naturalScale);\n renderX = dimensions.x;\n renderY = dimensions.y;\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n }\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 // NOTE: Do not close videoFrame - it's managed by RcFrame wrapper\n }\n\n private renderImageLayer(layer: ImageLayer): void {\n const { source, crop, renderConfig } = 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 return;\n }\n\n if (!source) return;\n\n const imgWidth = source.width;\n const imgHeight = source.height;\n\n let renderX: number;\n let renderY: number;\n let renderWidth: number;\n let renderHeight: number;\n\n if (renderConfig) {\n // Has renderConfig: explicit dimensions\n const dimensions = this.calculateDimensionsFromConfig(imgWidth, imgHeight, renderConfig);\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n // Images with renderConfig start at origin (for overlay positioning)\n renderX = 0;\n renderY = 0;\n } else {\n // No renderConfig: legacy smart fill (width-based, main track)\n const naturalScale = this.width / imgWidth;\n const dimensions = this.calculateRenderDimensions(imgWidth, imgHeight, naturalScale);\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n // Center the image\n renderX = Math.round((this.width - renderWidth) / 2);\n renderY = Math.round((this.height - renderHeight) / 2);\n }\n\n if (crop) {\n this.ctx.drawImage(\n source,\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(source, renderX, renderY, renderWidth, renderHeight);\n }\n }\n\n private renderTextLayer(layer: TextLayer): void {\n const animationType = layer.animation?.type;\n const hasWordTimings = layer.wordTimings && layer.wordTimings.length > 0;\n\n const needsWordTimings = ['wordByWord', 'characterKTV', 'wordByWordFancy'].includes(\n animationType || ''\n );\n\n if (needsWordTimings && !hasWordTimings) {\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n return;\n }\n\n switch (animationType) {\n case 'wordByWord':\n renderWordByWord(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'characterKTV':\n renderCharacterKTV(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'wordByWordFancy':\n renderWordByWordFancy(\n this.ctx,\n layer,\n this.width,\n this.height,\n this.currentFrame,\n this.fps\n );\n break;\n case 'fade':\n renderTextWithEntrance(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\n default:\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\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
|
+
{"version":3,"file":"LayerRenderer.js","sources":["../../../src/stages/compose/LayerRenderer.ts"],"sourcesContent":["import type { Layer, VideoLayer, ImageLayer, TextLayer, Transform2D, MaskConfig } from './types';\nimport { renderBasicText, renderTextWithEntrance } from './text-renderers/basic-text-renderer';\nimport { renderWordByWord } from './text-renderers/word-by-word-renderer';\nimport { renderCharacterKTV } from './text-renderers/character-ktv-renderer';\nimport { renderWordByWordFancy } from './text-renderers/word-fancy-renderer';\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 | CanvasRenderingContext2D;\n private width: number;\n private height: number;\n private currentFrame: number = 0;\n private fps: number = 30;\n private readonly FILL_THRESHOLD = 0.9;\n\n constructor(\n ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,\n width: number,\n height: number,\n fps: number = 30\n ) {\n this.ctx = ctx;\n this.width = width;\n this.height = height;\n this.fps = fps;\n this.ensureHighQualityRendering();\n }\n\n setCurrentFrame(frame: number): void {\n this.currentFrame = frame;\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 renderLayer(layer: Layer): void {\n if (!layer.visible || layer.opacity <= 0) return;\n\n // Only save/restore context if layer has properties that need it\n const needsStateManagement =\n layer.opacity !== 1 || layer.blendMode || layer.transform || layer.mask;\n\n if (needsStateManagement) {\n this.ctx.save();\n }\n\n try {\n // Apply layer properties only when needed\n if (layer.opacity !== 1) {\n this.ctx.globalAlpha = layer.opacity;\n }\n\n if (layer.blendMode) {\n this.ctx.globalCompositeOperation = layer.blendMode;\n }\n\n if (layer.transform) {\n // Get layer dimensions for transform anchor calculation\n const layerDimensions = this.getLayerDimensions(layer);\n this.applyTransform(layer.transform, layerDimensions);\n }\n // Render based on layer type\n switch (layer.type) {\n case 'video':\n this.renderVideoLayer(layer as VideoLayer);\n break;\n case 'image':\n this.renderImageLayer(layer as ImageLayer);\n break;\n case 'text':\n 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 if (needsStateManagement) {\n this.ctx.restore();\n }\n }\n }\n\n private parseDimension(\n value: number | string | undefined,\n canvasSize: number\n ): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'number') return value;\n\n // value is string at this point\n const strValue = value as string;\n\n // Parse percentage string like \"5%\"\n if (strValue.includes('%')) {\n const numValue = parseFloat(strValue);\n return isNaN(numValue) ? undefined : (numValue / 100) * canvasSize;\n }\n\n // Parse as pixel value\n const parsed = parseFloat(strValue);\n return isNaN(parsed) ? undefined : parsed;\n }\n\n /**\n * Calculate dimensions from renderConfig\n * Returns dimensions maintaining aspect ratio when only one dimension is specified\n */\n private calculateDimensionsFromConfig(\n sourceWidth: number,\n sourceHeight: number,\n renderConfig: { width?: number | string; height?: number | string } | undefined\n ): { width: number; height: number } {\n if (!renderConfig) {\n return { width: sourceWidth, height: sourceHeight };\n }\n\n const width = this.parseDimension(renderConfig.width, this.width);\n const height = this.parseDimension(renderConfig.height, this.height);\n\n if (width && height) {\n return { width, height };\n } else if (width) {\n return {\n width,\n height: Math.round((sourceHeight / sourceWidth) * width),\n };\n } else if (height) {\n return {\n width: Math.round((sourceWidth / sourceHeight) * height),\n height,\n };\n } else {\n // renderConfig exists but empty, use original size\n return { width: sourceWidth, height: sourceHeight };\n }\n }\n\n private getLayerDimensions(layer: Layer): { width: number; height: number } {\n if (layer.type === 'image') {\n const imageLayer = layer as ImageLayer;\n if (imageLayer.source) {\n const imgWidth = imageLayer.source.width;\n const imgHeight = imageLayer.source.height;\n\n // Check if has valid renderConfig\n const hasValidRenderConfig = !!(\n imageLayer.renderConfig?.width !== undefined ||\n imageLayer.renderConfig?.height !== undefined\n );\n\n if (hasValidRenderConfig) {\n return this.calculateDimensionsFromConfig(imgWidth, imgHeight, imageLayer.renderConfig);\n }\n // No valid renderConfig: return original dimensions\n return { width: imgWidth, height: imgHeight };\n }\n } else if (layer.type === 'video') {\n const videoLayer = layer as VideoLayer;\n const videoFrame = videoLayer.videoFrame;\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n // Check if has valid renderConfig\n const hasValidRenderConfig = !!(\n videoLayer.renderConfig?.width !== undefined ||\n videoLayer.renderConfig?.height !== undefined\n );\n\n if (hasValidRenderConfig) {\n return this.calculateDimensionsFromConfig(videoWidth, videoHeight, videoLayer.renderConfig);\n }\n // No valid renderConfig: return original dimensions\n return { width: videoWidth, height: videoHeight };\n }\n // Default to canvas dimensions\n return { width: this.width, height: this.height };\n }\n\n /**\n * Calculate render dimensions with smart fill logic\n * @param sourceWidth Source width\n * @param sourceHeight Source height\n * @param naturalScale Natural scale factor (scaleY for video, scaleX for image)\n * @returns Render dimensions and position\n */\n private calculateRenderDimensions(\n sourceWidth: number,\n sourceHeight: number,\n naturalScale: number\n ): { width: number; height: number; x: number; y: number } {\n const scaledWidth = sourceWidth * naturalScale;\n const scaledHeight = sourceHeight * naturalScale;\n\n // Smart fill: when scaled size is close to container (>90%), use cover mode\n const shouldFill =\n scaledWidth / this.width > this.FILL_THRESHOLD &&\n scaledHeight / this.height > this.FILL_THRESHOLD;\n\n let renderWidth: number;\n let renderHeight: number;\n\n if (shouldFill) {\n // Cover mode: use Math.max to ensure entire canvas is covered while maintaining aspect ratio\n const coverScale = Math.max(this.width / sourceWidth, this.height / sourceHeight);\n renderWidth = Math.round(sourceWidth * coverScale);\n renderHeight = Math.round(sourceHeight * coverScale);\n } else {\n // Natural scale mode: use scaled dimensions\n renderWidth = Math.round(scaledWidth);\n renderHeight = Math.round(scaledHeight);\n }\n\n // Center the content\n const renderX = Math.round((this.width - renderWidth) / 2);\n const renderY = Math.round((this.height - renderHeight) / 2);\n\n return { width: renderWidth, height: renderHeight, x: renderX, y: renderY };\n }\n\n private applyTransform(\n transform: Transform2D,\n layerDimensions: { width: number; height: number }\n ): void {\n // Use layer dimensions (not canvas dimensions) for anchor calculation\n const anchorX = transform.anchorX ?? 0.5;\n const anchorY = transform.anchorY ?? 0.5;\n const centerX = layerDimensions.width * anchorX;\n const centerY = layerDimensions.height * anchorY;\n\n // Move to the layer position + anchor offset\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 // Move back by anchor offset\n this.ctx.translate(-centerX, -centerY);\n }\n\n private renderVideoLayer(layer: VideoLayer): void {\n const { videoFrame, crop, renderConfig } = layer;\n\n const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;\n const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;\n\n let renderX: number;\n let renderY: number;\n let renderWidth: number;\n let renderHeight: number;\n\n // Check if has valid renderConfig (with actual width or height values)\n const hasValidRenderConfig = !!(\n renderConfig?.width !== undefined || renderConfig?.height !== undefined\n );\n\n if (hasValidRenderConfig) {\n // Has valid renderConfig: explicit dimensions\n const dimensions = this.calculateDimensionsFromConfig(videoWidth, videoHeight, renderConfig);\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n // Center the video\n renderX = Math.round((this.width - renderWidth) / 2);\n renderY = Math.round((this.height - renderHeight) / 2);\n } else {\n // No valid renderConfig: legacy smart fill (height-based)\n const naturalScale = this.height / videoHeight;\n const dimensions = this.calculateRenderDimensions(videoWidth, videoHeight, naturalScale);\n renderX = dimensions.x;\n renderY = dimensions.y;\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n }\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 // NOTE: Do not close videoFrame - it's managed by RcFrame wrapper\n }\n\n private renderImageLayer(layer: ImageLayer): void {\n const { source, crop, renderConfig, attachmentId } = 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 return;\n }\n\n if (!source) return;\n\n const imgWidth = source.width;\n const imgHeight = source.height;\n\n let renderX: number;\n let renderY: number;\n let renderWidth: number;\n let renderHeight: number;\n\n // Check if has valid renderConfig (with actual width or height values)\n const hasValidRenderConfig = !!(\n renderConfig?.width !== undefined || renderConfig?.height !== undefined\n );\n\n if (hasValidRenderConfig) {\n // Has valid renderConfig: explicit dimensions\n const dimensions = this.calculateDimensionsFromConfig(imgWidth, imgHeight, renderConfig);\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n // Images with renderConfig start at origin (for overlay positioning via transform)\n renderX = 0;\n renderY = 0;\n } else if (attachmentId) {\n // Attachment without valid renderConfig: use original size, start at origin\n renderWidth = imgWidth;\n renderHeight = imgHeight;\n renderX = 0;\n renderY = 0;\n } else {\n // No renderConfig and not attachment: legacy smart fill (width-based, main track)\n const naturalScale = this.width / imgWidth;\n const dimensions = this.calculateRenderDimensions(imgWidth, imgHeight, naturalScale);\n renderWidth = dimensions.width;\n renderHeight = dimensions.height;\n // Center the image\n renderX = Math.round((this.width - renderWidth) / 2);\n renderY = Math.round((this.height - renderHeight) / 2);\n }\n\n if (crop) {\n this.ctx.drawImage(\n source,\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(source, renderX, renderY, renderWidth, renderHeight);\n }\n }\n\n private renderTextLayer(layer: TextLayer): void {\n const animationType = layer.animation?.type;\n const hasWordTimings = layer.wordTimings && layer.wordTimings.length > 0;\n\n const needsWordTimings = ['wordByWord', 'characterKTV', 'wordByWordFancy'].includes(\n animationType || ''\n );\n\n if (needsWordTimings && !hasWordTimings) {\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n return;\n }\n\n switch (animationType) {\n case 'wordByWord':\n renderWordByWord(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'characterKTV':\n renderCharacterKTV(this.ctx, layer, this.width, this.height, this.currentFrame, this.fps);\n break;\n case 'wordByWordFancy':\n renderWordByWordFancy(\n this.ctx,\n layer,\n this.width,\n this.height,\n this.currentFrame,\n this.fps\n );\n break;\n case 'fade':\n renderTextWithEntrance(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\n default:\n renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);\n break;\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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"}
|
|
@@ -31,4 +31,18 @@ export declare function isPlainObject(value: any): value is Record<string, any>;
|
|
|
31
31
|
* Clone an object deeply
|
|
32
32
|
*/
|
|
33
33
|
export declare function cloneDeep<T>(obj: T): T;
|
|
34
|
+
/**
|
|
35
|
+
* Filter renderConfig to only include valid width/height fields
|
|
36
|
+
* Warns if renderConfig exists but has no valid fields or all fields are zero
|
|
37
|
+
* @param renderConfig - The renderConfig object to filter
|
|
38
|
+
* @param context - Context string for warning message (e.g., 'clip-123', 'attachment-456')
|
|
39
|
+
* @returns Filtered renderConfig with only valid fields, or undefined if no valid fields
|
|
40
|
+
*/
|
|
41
|
+
export declare function filterRenderConfig(renderConfig: {
|
|
42
|
+
width?: number | string;
|
|
43
|
+
height?: number | string;
|
|
44
|
+
} | undefined, context?: string): {
|
|
45
|
+
width?: number | string;
|
|
46
|
+
height?: number | string;
|
|
47
|
+
} | undefined;
|
|
34
48
|
//# sourceMappingURL=object-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object-utils.d.ts","sourceRoot":"","sources":["../../src/utils/object-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAgCpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3D,QAAQ,EAAE,CAAC,EACX,GAAG,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GACjD,CAAC,CAEH;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAYnF;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,CAiBjG;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAgBtC"}
|
|
1
|
+
{"version":3,"file":"object-utils.d.ts","sourceRoot":"","sources":["../../src/utils/object-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAgCpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3D,QAAQ,EAAE,CAAC,EACX,GAAG,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GACjD,CAAC,CAEH;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAYnF;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,CAiBjG;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAgBtC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,SAAS,EAC/E,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAAG,SAAS,CAoCnE"}
|
|
@@ -16,7 +16,33 @@ function deepMerge(target, ...sources) {
|
|
|
16
16
|
}
|
|
17
17
|
return result;
|
|
18
18
|
}
|
|
19
|
+
function filterRenderConfig(renderConfig, context) {
|
|
20
|
+
if (!renderConfig) {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
const hasValidWidth = renderConfig.width !== void 0 && (typeof renderConfig.width === "number" || typeof renderConfig.width === "string");
|
|
24
|
+
const hasValidHeight = renderConfig.height !== void 0 && (typeof renderConfig.height === "number" || typeof renderConfig.height === "string");
|
|
25
|
+
const bothZero = (renderConfig.width === 0 || renderConfig.width === "0") && (renderConfig.height === 0 || renderConfig.height === "0");
|
|
26
|
+
if (bothZero) {
|
|
27
|
+
console.warn(
|
|
28
|
+
`[filterRenderConfig] renderConfig has width and height both set to 0${context ? ` for ${context}` : ""}`,
|
|
29
|
+
renderConfig
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (!hasValidWidth && !hasValidHeight) {
|
|
33
|
+
console.warn(
|
|
34
|
+
`[filterRenderConfig] renderConfig exists but has no valid width/height${context ? ` for ${context}` : ""}`,
|
|
35
|
+
renderConfig
|
|
36
|
+
);
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...hasValidWidth && { width: renderConfig.width },
|
|
41
|
+
...hasValidHeight && { height: renderConfig.height }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
19
44
|
export {
|
|
20
|
-
deepMerge
|
|
45
|
+
deepMerge,
|
|
46
|
+
filterRenderConfig
|
|
21
47
|
};
|
|
22
48
|
//# sourceMappingURL=object-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object-utils.js","sources":["../../src/utils/object-utils.ts"],"sourcesContent":["/**\n * Deep merge two objects recursively\n * Later values override earlier ones\n * Supports merging objects with different but compatible types\n */\nexport function deepMerge<T = any>(target: any, ...sources: any[]): T {\n if (!sources.length) return target as T;\n\n const result = { ...target };\n\n for (const source of sources) {\n if (!source) continue;\n\n for (const key in source) {\n const sourceValue = source[key];\n const targetValue = result[key];\n\n if (sourceValue === undefined) continue;\n\n if (\n sourceValue !== null &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue !== null &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n // Recursively merge objects\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n // Direct assignment for primitives and arrays\n result[key] = sourceValue;\n }\n }\n }\n\n return result as T;\n}\n\n/**\n * Type-safe deep merge for configuration objects\n * Ensures the result conforms to the target type structure\n */\nexport function deepMergeConfig<T extends Record<string, any>>(\n defaults: T,\n ...overrides: Array<Partial<Record<keyof T, any>>>\n): T {\n return deepMerge<T>(defaults, ...overrides);\n}\n\n/**\n * Get a nested value from an object using a path\n * @param obj The object to query\n * @param path Path to the value (e.g., 'a.b.c' or ['a', 'b', 'c'])\n * @param defaultValue Default value if path doesn't exist\n */\nexport function get<T = any>(obj: any, path: string | string[], defaultValue?: T): T {\n const keys = Array.isArray(path) ? path : path.split('.');\n let current = obj;\n\n for (const key of keys) {\n if (current == null || typeof current !== 'object') {\n return defaultValue as T;\n }\n current = current[key];\n }\n\n return current !== undefined ? current : (defaultValue as T);\n}\n\n/**\n * Set a nested value in an object using a path\n * @param obj The object to modify\n * @param path Path to the value (e.g., 'a.b.c' or ['a', 'b', 'c'])\n * @param value The value to set\n */\nexport function set<T extends Record<string, any>>(obj: T, path: string | string[], value: any): T {\n const keys = Array.isArray(path) ? path : path.split('.');\n const lastKey = keys.pop();\n\n if (!lastKey) return obj;\n\n let current: any = obj;\n\n for (const key of keys) {\n if (!current[key] || typeof current[key] !== 'object') {\n current[key] = {};\n }\n current = current[key];\n }\n\n current[lastKey] = value;\n return obj;\n}\n\n/**\n * Check if a value is a plain object\n */\nexport function isPlainObject(value: any): value is Record<string, any> {\n return (\n value !== null &&\n typeof value === 'object' &&\n value.constructor === Object &&\n Object.prototype.toString.call(value) === '[object Object]'\n );\n}\n\n/**\n * Clone an object deeply\n */\nexport function cloneDeep<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') return obj;\n if (obj instanceof Date) return new Date(obj.getTime()) as any;\n if (obj instanceof Array) return obj.map((item) => cloneDeep(item)) as any;\n if (obj instanceof Set) return new Set(Array.from(obj).map((item) => cloneDeep(item))) as any;\n if (obj instanceof Map) {\n return new Map(Array.from(obj.entries()).map(([k, v]) => [cloneDeep(k), cloneDeep(v)])) as any;\n }\n\n const cloned = {} as T;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n cloned[key] = cloneDeep(obj[key]);\n }\n }\n return cloned;\n}\n"],"names":[],"mappings":"AAKO,SAAS,UAAmB,WAAgB,SAAmB;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,QAAM,SAAS,EAAE,GAAG,OAAA;AAEpB,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,OAAQ;AAEb,eAAW,OAAO,QAAQ;AACxB,YAAM,cAAc,OAAO,GAAG;AAC9B,YAAM,cAAc,OAAO,GAAG;AAE9B,UAAI,gBAAgB,OAAW;AAE/B,UACE,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,KAC1B,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,GAC1B;AAEA,eAAO,GAAG,IAAI,UAAU,aAAa,WAAW;AAAA,MAClD,OAAO;AAEL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;"}
|
|
1
|
+
{"version":3,"file":"object-utils.js","sources":["../../src/utils/object-utils.ts"],"sourcesContent":["/**\n * Deep merge two objects recursively\n * Later values override earlier ones\n * Supports merging objects with different but compatible types\n */\nexport function deepMerge<T = any>(target: any, ...sources: any[]): T {\n if (!sources.length) return target as T;\n\n const result = { ...target };\n\n for (const source of sources) {\n if (!source) continue;\n\n for (const key in source) {\n const sourceValue = source[key];\n const targetValue = result[key];\n\n if (sourceValue === undefined) continue;\n\n if (\n sourceValue !== null &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue !== null &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n // Recursively merge objects\n result[key] = deepMerge(targetValue, sourceValue);\n } else {\n // Direct assignment for primitives and arrays\n result[key] = sourceValue;\n }\n }\n }\n\n return result as T;\n}\n\n/**\n * Type-safe deep merge for configuration objects\n * Ensures the result conforms to the target type structure\n */\nexport function deepMergeConfig<T extends Record<string, any>>(\n defaults: T,\n ...overrides: Array<Partial<Record<keyof T, any>>>\n): T {\n return deepMerge<T>(defaults, ...overrides);\n}\n\n/**\n * Get a nested value from an object using a path\n * @param obj The object to query\n * @param path Path to the value (e.g., 'a.b.c' or ['a', 'b', 'c'])\n * @param defaultValue Default value if path doesn't exist\n */\nexport function get<T = any>(obj: any, path: string | string[], defaultValue?: T): T {\n const keys = Array.isArray(path) ? path : path.split('.');\n let current = obj;\n\n for (const key of keys) {\n if (current == null || typeof current !== 'object') {\n return defaultValue as T;\n }\n current = current[key];\n }\n\n return current !== undefined ? current : (defaultValue as T);\n}\n\n/**\n * Set a nested value in an object using a path\n * @param obj The object to modify\n * @param path Path to the value (e.g., 'a.b.c' or ['a', 'b', 'c'])\n * @param value The value to set\n */\nexport function set<T extends Record<string, any>>(obj: T, path: string | string[], value: any): T {\n const keys = Array.isArray(path) ? path : path.split('.');\n const lastKey = keys.pop();\n\n if (!lastKey) return obj;\n\n let current: any = obj;\n\n for (const key of keys) {\n if (!current[key] || typeof current[key] !== 'object') {\n current[key] = {};\n }\n current = current[key];\n }\n\n current[lastKey] = value;\n return obj;\n}\n\n/**\n * Check if a value is a plain object\n */\nexport function isPlainObject(value: any): value is Record<string, any> {\n return (\n value !== null &&\n typeof value === 'object' &&\n value.constructor === Object &&\n Object.prototype.toString.call(value) === '[object Object]'\n );\n}\n\n/**\n * Clone an object deeply\n */\nexport function cloneDeep<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') return obj;\n if (obj instanceof Date) return new Date(obj.getTime()) as any;\n if (obj instanceof Array) return obj.map((item) => cloneDeep(item)) as any;\n if (obj instanceof Set) return new Set(Array.from(obj).map((item) => cloneDeep(item))) as any;\n if (obj instanceof Map) {\n return new Map(Array.from(obj.entries()).map(([k, v]) => [cloneDeep(k), cloneDeep(v)])) as any;\n }\n\n const cloned = {} as T;\n for (const key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n cloned[key] = cloneDeep(obj[key]);\n }\n }\n return cloned;\n}\n\n/**\n * Filter renderConfig to only include valid width/height fields\n * Warns if renderConfig exists but has no valid fields or all fields are zero\n * @param renderConfig - The renderConfig object to filter\n * @param context - Context string for warning message (e.g., 'clip-123', 'attachment-456')\n * @returns Filtered renderConfig with only valid fields, or undefined if no valid fields\n */\nexport function filterRenderConfig(\n renderConfig: { width?: number | string; height?: number | string } | undefined,\n context?: string\n): { width?: number | string; height?: number | string } | undefined {\n if (!renderConfig) {\n return undefined;\n }\n\n const hasValidWidth =\n renderConfig.width !== undefined &&\n (typeof renderConfig.width === 'number' || typeof renderConfig.width === 'string');\n const hasValidHeight =\n renderConfig.height !== undefined &&\n (typeof renderConfig.height === 'number' || typeof renderConfig.height === 'string');\n\n // Check if both are zero\n const bothZero =\n (renderConfig.width === 0 || renderConfig.width === '0') &&\n (renderConfig.height === 0 || renderConfig.height === '0');\n\n if (bothZero) {\n console.warn(\n `[filterRenderConfig] renderConfig has width and height both set to 0${context ? ` for ${context}` : ''}`,\n renderConfig\n );\n }\n\n if (!hasValidWidth && !hasValidHeight) {\n console.warn(\n `[filterRenderConfig] renderConfig exists but has no valid width/height${context ? ` for ${context}` : ''}`,\n renderConfig\n );\n return undefined;\n }\n\n return {\n ...(hasValidWidth && { width: renderConfig.width }),\n ...(hasValidHeight && { height: renderConfig.height }),\n };\n}\n"],"names":[],"mappings":"AAKO,SAAS,UAAmB,WAAgB,SAAmB;AACpE,MAAI,CAAC,QAAQ,OAAQ,QAAO;AAE5B,QAAM,SAAS,EAAE,GAAG,OAAA;AAEpB,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,OAAQ;AAEb,eAAW,OAAO,QAAQ;AACxB,YAAM,cAAc,OAAO,GAAG;AAC9B,YAAM,cAAc,OAAO,GAAG;AAE9B,UAAI,gBAAgB,OAAW;AAE/B,UACE,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,KAC1B,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,GAC1B;AAEA,eAAO,GAAG,IAAI,UAAU,aAAa,WAAW;AAAA,MAClD,OAAO;AAEL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAkGO,SAAS,mBACd,cACA,SACmE;AACnE,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,gBACJ,aAAa,UAAU,WACtB,OAAO,aAAa,UAAU,YAAY,OAAO,aAAa,UAAU;AAC3E,QAAM,iBACJ,aAAa,WAAW,WACvB,OAAO,aAAa,WAAW,YAAY,OAAO,aAAa,WAAW;AAG7E,QAAM,YACH,aAAa,UAAU,KAAK,aAAa,UAAU,SACnD,aAAa,WAAW,KAAK,aAAa,WAAW;AAExD,MAAI,UAAU;AACZ,YAAQ;AAAA,MACN,uEAAuE,UAAU,QAAQ,OAAO,KAAK,EAAE;AAAA,MACvG;AAAA,IAAA;AAAA,EAEJ;AAEA,MAAI,CAAC,iBAAiB,CAAC,gBAAgB;AACrC,YAAQ;AAAA,MACN,yEAAyE,UAAU,QAAQ,OAAO,KAAK,EAAE;AAAA,MACzG;AAAA,IAAA;AAEF,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,GAAI,iBAAiB,EAAE,OAAO,aAAa,MAAA;AAAA,IAC3C,GAAI,kBAAkB,EAAE,QAAQ,aAAa,OAAA;AAAA,EAAO;AAExD;"}
|
|
@@ -875,14 +875,22 @@ class LayerRenderer {
|
|
|
875
875
|
if (imageLayer.source) {
|
|
876
876
|
const imgWidth = imageLayer.source.width;
|
|
877
877
|
const imgHeight = imageLayer.source.height;
|
|
878
|
-
|
|
878
|
+
const hasValidRenderConfig = !!(imageLayer.renderConfig?.width !== void 0 || imageLayer.renderConfig?.height !== void 0);
|
|
879
|
+
if (hasValidRenderConfig) {
|
|
880
|
+
return this.calculateDimensionsFromConfig(imgWidth, imgHeight, imageLayer.renderConfig);
|
|
881
|
+
}
|
|
882
|
+
return { width: imgWidth, height: imgHeight };
|
|
879
883
|
}
|
|
880
884
|
} else if (layer.type === "video") {
|
|
881
885
|
const videoLayer = layer;
|
|
882
886
|
const videoFrame = videoLayer.videoFrame;
|
|
883
887
|
const videoWidth = videoFrame.displayWidth || videoFrame.codedWidth;
|
|
884
888
|
const videoHeight = videoFrame.displayHeight || videoFrame.codedHeight;
|
|
885
|
-
|
|
889
|
+
const hasValidRenderConfig = !!(videoLayer.renderConfig?.width !== void 0 || videoLayer.renderConfig?.height !== void 0);
|
|
890
|
+
if (hasValidRenderConfig) {
|
|
891
|
+
return this.calculateDimensionsFromConfig(videoWidth, videoHeight, videoLayer.renderConfig);
|
|
892
|
+
}
|
|
893
|
+
return { width: videoWidth, height: videoHeight };
|
|
886
894
|
}
|
|
887
895
|
return { width: this.width, height: this.height };
|
|
888
896
|
}
|
|
@@ -934,7 +942,8 @@ class LayerRenderer {
|
|
|
934
942
|
let renderY;
|
|
935
943
|
let renderWidth;
|
|
936
944
|
let renderHeight;
|
|
937
|
-
|
|
945
|
+
const hasValidRenderConfig = !!(renderConfig?.width !== void 0 || renderConfig?.height !== void 0);
|
|
946
|
+
if (hasValidRenderConfig) {
|
|
938
947
|
const dimensions = this.calculateDimensionsFromConfig(videoWidth, videoHeight, renderConfig);
|
|
939
948
|
renderWidth = dimensions.width;
|
|
940
949
|
renderHeight = dimensions.height;
|
|
@@ -965,7 +974,7 @@ class LayerRenderer {
|
|
|
965
974
|
}
|
|
966
975
|
}
|
|
967
976
|
renderImageLayer(layer) {
|
|
968
|
-
const { source, crop, renderConfig } = layer;
|
|
977
|
+
const { source, crop, renderConfig, attachmentId } = layer;
|
|
969
978
|
if (source instanceof ImageData) {
|
|
970
979
|
if (crop) {
|
|
971
980
|
const tempCanvas = new OffscreenCanvas(crop.width, crop.height);
|
|
@@ -984,12 +993,18 @@ class LayerRenderer {
|
|
|
984
993
|
let renderY;
|
|
985
994
|
let renderWidth;
|
|
986
995
|
let renderHeight;
|
|
987
|
-
|
|
996
|
+
const hasValidRenderConfig = !!(renderConfig?.width !== void 0 || renderConfig?.height !== void 0);
|
|
997
|
+
if (hasValidRenderConfig) {
|
|
988
998
|
const dimensions = this.calculateDimensionsFromConfig(imgWidth, imgHeight, renderConfig);
|
|
989
999
|
renderWidth = dimensions.width;
|
|
990
1000
|
renderHeight = dimensions.height;
|
|
991
1001
|
renderX = 0;
|
|
992
1002
|
renderY = 0;
|
|
1003
|
+
} else if (attachmentId) {
|
|
1004
|
+
renderWidth = imgWidth;
|
|
1005
|
+
renderHeight = imgHeight;
|
|
1006
|
+
renderX = 0;
|
|
1007
|
+
renderY = 0;
|
|
993
1008
|
} else {
|
|
994
1009
|
const naturalScale = this.width / imgWidth;
|
|
995
1010
|
const dimensions = this.calculateRenderDimensions(imgWidth, imgHeight, naturalScale);
|
|
@@ -2012,6 +2027,31 @@ class FrameRateConverter {
|
|
|
2012
2027
|
};
|
|
2013
2028
|
}
|
|
2014
2029
|
}
|
|
2030
|
+
function filterRenderConfig(renderConfig, context) {
|
|
2031
|
+
if (!renderConfig) {
|
|
2032
|
+
return void 0;
|
|
2033
|
+
}
|
|
2034
|
+
const hasValidWidth = renderConfig.width !== void 0 && (typeof renderConfig.width === "number" || typeof renderConfig.width === "string");
|
|
2035
|
+
const hasValidHeight = renderConfig.height !== void 0 && (typeof renderConfig.height === "number" || typeof renderConfig.height === "string");
|
|
2036
|
+
const bothZero = (renderConfig.width === 0 || renderConfig.width === "0") && (renderConfig.height === 0 || renderConfig.height === "0");
|
|
2037
|
+
if (bothZero) {
|
|
2038
|
+
console.warn(
|
|
2039
|
+
`[filterRenderConfig] renderConfig has width and height both set to 0${context ? ` for ${context}` : ""}`,
|
|
2040
|
+
renderConfig
|
|
2041
|
+
);
|
|
2042
|
+
}
|
|
2043
|
+
if (!hasValidWidth && !hasValidHeight) {
|
|
2044
|
+
console.warn(
|
|
2045
|
+
`[filterRenderConfig] renderConfig exists but has no valid width/height${context ? ` for ${context}` : ""}`,
|
|
2046
|
+
renderConfig
|
|
2047
|
+
);
|
|
2048
|
+
return void 0;
|
|
2049
|
+
}
|
|
2050
|
+
return {
|
|
2051
|
+
...hasValidWidth && { width: renderConfig.width },
|
|
2052
|
+
...hasValidHeight && { height: renderConfig.height }
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2015
2055
|
function resolveActiveLayers(layers, timestamp) {
|
|
2016
2056
|
return layers.filter((layer) => {
|
|
2017
2057
|
return layer.activeRanges.some(
|
|
@@ -2029,12 +2069,16 @@ function materializeLayer(layer, frame, imageMap, globalTimeUs) {
|
|
|
2029
2069
|
};
|
|
2030
2070
|
if (layer.type === "video") {
|
|
2031
2071
|
const payload = layer.payload;
|
|
2032
|
-
|
|
2072
|
+
const videoLayer = {
|
|
2033
2073
|
...baseLayer,
|
|
2034
2074
|
type: "video",
|
|
2035
|
-
videoFrame: frame
|
|
2036
|
-
renderConfig: payload.renderConfig
|
|
2075
|
+
videoFrame: frame
|
|
2037
2076
|
};
|
|
2077
|
+
const filteredRenderConfig = filterRenderConfig(payload.renderConfig, "video layer");
|
|
2078
|
+
if (filteredRenderConfig) {
|
|
2079
|
+
videoLayer.renderConfig = filteredRenderConfig;
|
|
2080
|
+
}
|
|
2081
|
+
return videoLayer;
|
|
2038
2082
|
}
|
|
2039
2083
|
if (layer.type === "text") {
|
|
2040
2084
|
const payload = layer.payload;
|
|
@@ -2057,9 +2101,15 @@ function materializeLayer(layer, frame, imageMap, globalTimeUs) {
|
|
|
2057
2101
|
...baseLayer,
|
|
2058
2102
|
type: "image",
|
|
2059
2103
|
source,
|
|
2060
|
-
attachmentId: payload.attachmentId
|
|
2061
|
-
renderConfig: payload.renderConfig
|
|
2104
|
+
attachmentId: payload.attachmentId
|
|
2062
2105
|
};
|
|
2106
|
+
const filteredRenderConfig = filterRenderConfig(
|
|
2107
|
+
payload.renderConfig,
|
|
2108
|
+
`image layer ${payload.attachmentId || "unknown"}`
|
|
2109
|
+
);
|
|
2110
|
+
if (filteredRenderConfig) {
|
|
2111
|
+
imageLayer.renderConfig = filteredRenderConfig;
|
|
2112
|
+
}
|
|
2063
2113
|
if (payload.animation && globalTimeUs !== void 0) {
|
|
2064
2114
|
const animState = computeAnimationState(payload.animation, globalTimeUs);
|
|
2065
2115
|
if (!animState.visible) {
|
|
@@ -2461,4 +2511,4 @@ export {
|
|
|
2461
2511
|
VideoComposeWorker,
|
|
2462
2512
|
videoCompose_worker as default
|
|
2463
2513
|
};
|
|
2464
|
-
//# sourceMappingURL=video-compose.worker.
|
|
2514
|
+
//# sourceMappingURL=video-compose.worker.CQwmNfXT.js.map
|