@editframe/elements 0.31.2-beta.0 → 0.32.0-beta.0
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/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/preview/previewSettings.js +5 -0
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.d.ts +2 -1
- package/dist/preview/renderTimegroupToVideo.js +15 -7
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/render/EFRenderAPI.js +67 -0
- package/dist/render/EFRenderAPI.js.map +1 -0
- package/dist/render/getRenderData.d.ts +31 -0
- package/dist/render/getRenderData.js +29 -0
- package/dist/render/getRenderData.js.map +1 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { TemporalMixinInterface, isEFTemporal } from "./elements/EFTemporal.js";
|
|
|
3
3
|
import { EFMedia } from "./elements/EFMedia.js";
|
|
4
4
|
import { ContainerInfo, getContainerInfoFromElement } from "./elements/ContainerInfo.js";
|
|
5
5
|
import { ElementPositionInfo, PositionInfoMixin, getPositionInfoFromElement } from "./elements/ElementPositionInfo.js";
|
|
6
|
+
import { RenderProgress, RenderToVideoOptions } from "./preview/renderTimegroupToVideo.js";
|
|
6
7
|
import { CanvasElementBounds, CanvasElementData, SelectionState } from "./canvas/api/types.js";
|
|
7
8
|
import { EFPanZoom, PanZoomTransform } from "./elements/EFPanZoom.js";
|
|
8
9
|
import { EFOverlayItem, OverlayItemPosition } from "./gui/EFOverlayItem.js";
|
|
@@ -50,4 +51,5 @@ import { EFCanvasItem } from "./canvas/EFCanvasItem.js";
|
|
|
50
51
|
import { CanvasAPI } from "./canvas/api/CanvasAPI.js";
|
|
51
52
|
import { SelectionModel } from "./canvas/selection/SelectionModel.js";
|
|
52
53
|
import { RenderInfo, getRenderInfo } from "./getRenderInfo.js";
|
|
53
|
-
|
|
54
|
+
import { getRenderData } from "./render/getRenderData.js";
|
|
55
|
+
export { type BoxBounds, CanvasAPI, type CanvasElementBounds, type CanvasElementData, type ContainerInfo, type DialChangeDetail, EFActiveRootTemporal, EFAudio, EFAudioHierarchyItem, EFCanvas, EFCanvasItem, EFCaptions, EFCaptionsActiveWord, EFCaptionsActiveWordHierarchyItem, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsHierarchyItem, EFCaptionsSegment, EFConfiguration, EFControls, EFDial, EFFilmstrip, EFFitScale, EFFocusOverlay, EFHTMLHierarchyItem, EFHierarchy, EFHierarchyItem, EFImage, EFImageHierarchyItem, type EFMedia, EFOverlayItem, EFOverlayLayer, EFPanZoom, EFPause, EFPlay, EFPreview, EFResizableBox, EFScrubber, EFSurface, EFText, EFTextHierarchyItem, EFTextSegment, EFTextSegmentHierarchyItem, EFThumbnailStrip, EFTimeDisplay, EFTimegroup, EFTimegroupHierarchyItem, EFTimeline, EFTimelineRuler, EFToggleLoop, EFTogglePlay, EFTransformHandles, EFTree, EFTreeItem, EFTrimHandles, EFVideo, EFVideoHierarchyItem, EFWaveform, EFWaveformHierarchyItem, EFWorkbench, type ElementPositionInfo, type HierarchyActions, type HierarchyContext, type HierarchyState, type OverlayItemPosition, type PanZoomTransform, PositionInfoMixin, RenderInfo, type RenderProgress, type RenderToVideoOptions, SelectionModel, type SelectionState, type TemporalMixinInterface, type TraceContext, type TransformBounds, type TreeActions, type TreeContext, type TreeItem, type TreeState, type TrimChangeDetail, calculateFrameIntervalMs, calculatePixelsPerFrame, collectAllIds, elementNeedsFitScale, getContainerInfoFromElement, getCornerPoint, getOppositeCorner, getPositionInfoFromElement, getRenderData, getRenderInfo, hierarchyContext, isEFTemporal, needsFitScale, quantizeToFrameTimeMs, rotatePoint, shouldShowFrameMarkers, treeContext };
|
package/dist/index.js
CHANGED
|
@@ -50,10 +50,12 @@ import { EFCanvasItem } from "./canvas/EFCanvasItem.js";
|
|
|
50
50
|
import { EFOverlayItem } from "./gui/EFOverlayItem.js";
|
|
51
51
|
import "./EF_FRAMEGEN.js";
|
|
52
52
|
import { RenderInfo, getRenderInfo } from "./getRenderInfo.js";
|
|
53
|
+
import "./render/EFRenderAPI.js";
|
|
54
|
+
import { getRenderData } from "./render/getRenderData.js";
|
|
53
55
|
|
|
54
56
|
//#region src/index.ts
|
|
55
57
|
if (typeof window !== "undefined") window.EF_REGISTERED = true;
|
|
56
58
|
|
|
57
59
|
//#endregion
|
|
58
|
-
export { CanvasAPI, EFActiveRootTemporal, EFAudio, EFAudioHierarchyItem, EFCanvas, EFCanvasItem, EFCaptions, EFCaptionsActiveWord, EFCaptionsActiveWordHierarchyItem, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsHierarchyItem, EFCaptionsSegment, EFConfiguration, EFControls, EFDial, EFFilmstrip, EFFitScale, EFFocusOverlay, EFHTMLHierarchyItem, EFHierarchy, EFHierarchyItem, EFImage, EFImageHierarchyItem, EFOverlayItem, EFOverlayLayer, EFPanZoom, EFPause, EFPlay, EFPreview, EFResizableBox, EFScrubber, EFSurface, EFText, EFTextHierarchyItem, EFTextSegment, EFTextSegmentHierarchyItem, EFThumbnailStrip, EFTimeDisplay, EFTimegroup, EFTimegroupHierarchyItem, EFTimeline, EFTimelineRuler, EFToggleLoop, EFTogglePlay, EFTransformHandles, EFTree, EFTreeItem, EFTrimHandles, EFVideo, EFVideoHierarchyItem, EFWaveform, EFWaveformHierarchyItem, EFWorkbench, PositionInfoMixin, RenderInfo, SelectionModel, calculateFrameIntervalMs, calculatePixelsPerFrame, collectAllIds, elementNeedsFitScale, getContainerInfoFromElement, getCornerPoint, getOppositeCorner, getPositionInfoFromElement, getRenderInfo, hierarchyContext, isEFTemporal, needsFitScale, quantizeToFrameTimeMs, rotatePoint, shouldShowFrameMarkers, treeContext };
|
|
60
|
+
export { CanvasAPI, EFActiveRootTemporal, EFAudio, EFAudioHierarchyItem, EFCanvas, EFCanvasItem, EFCaptions, EFCaptionsActiveWord, EFCaptionsActiveWordHierarchyItem, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsHierarchyItem, EFCaptionsSegment, EFConfiguration, EFControls, EFDial, EFFilmstrip, EFFitScale, EFFocusOverlay, EFHTMLHierarchyItem, EFHierarchy, EFHierarchyItem, EFImage, EFImageHierarchyItem, EFOverlayItem, EFOverlayLayer, EFPanZoom, EFPause, EFPlay, EFPreview, EFResizableBox, EFScrubber, EFSurface, EFText, EFTextHierarchyItem, EFTextSegment, EFTextSegmentHierarchyItem, EFThumbnailStrip, EFTimeDisplay, EFTimegroup, EFTimegroupHierarchyItem, EFTimeline, EFTimelineRuler, EFToggleLoop, EFTogglePlay, EFTransformHandles, EFTree, EFTreeItem, EFTrimHandles, EFVideo, EFVideoHierarchyItem, EFWaveform, EFWaveformHierarchyItem, EFWorkbench, PositionInfoMixin, RenderInfo, SelectionModel, calculateFrameIntervalMs, calculatePixelsPerFrame, collectAllIds, elementNeedsFitScale, getContainerInfoFromElement, getCornerPoint, getOppositeCorner, getPositionInfoFromElement, getRenderData, getRenderInfo, hierarchyContext, isEFTemporal, needsFitScale, quantizeToFrameTimeMs, rotatePoint, shouldShowFrameMarkers, treeContext };
|
|
59
61
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import \"./elements/EFTimegroup.js\";\nimport \"./sandbox/index.js\";\n\nexport { EFTimegroup } from \"./elements/EFTimegroup.js\";\nexport type { ContainerInfo } from \"./elements/ContainerInfo.js\";\nexport { getContainerInfoFromElement } from \"./elements/ContainerInfo.js\";\nexport type { ElementPositionInfo } from \"./elements/ElementPositionInfo.js\";\nexport {\n getPositionInfoFromElement,\n PositionInfoMixin,\n} from \"./elements/ElementPositionInfo.js\";\nexport { needsFitScale, elementNeedsFitScale } from \"./gui/FitScaleHelpers.js\";\n\nimport \"./elements/EFImage.js\";\n\nexport { EFImage } from \"./elements/EFImage.js\";\n\nimport \"./elements/EFMedia.js\";\n\nexport type { EFMedia } from \"./elements/EFMedia.js\";\n\nimport \"./elements/EFAudio.js\";\n\nexport { EFAudio } from \"./elements/EFAudio.js\";\n\nimport \"./elements/EFVideo.js\";\n\nexport { EFVideo } from \"./elements/EFVideo.js\";\n\nimport \"./elements/EFCaptions.js\";\n\nexport {\n EFCaptions,\n EFCaptionsActiveWord,\n EFCaptionsAfterActiveWord,\n EFCaptionsBeforeActiveWord,\n EFCaptionsSegment,\n} from \"./elements/EFCaptions.js\";\n\nimport \"./elements/EFText.js\";\nimport \"./elements/EFTextSegment.js\";\n\nexport { EFText } from \"./elements/EFText.js\";\nexport { EFTextSegment } from \"./elements/EFTextSegment.js\";\n\nimport \"./elements/EFWaveform.js\";\n\nexport { EFWaveform } from \"./elements/EFWaveform.js\";\n\nimport \"./elements/EFTemporal.js\";\n\nexport { isEFTemporal } from \"./elements/EFTemporal.js\";\nexport type { TemporalMixinInterface } from \"./elements/EFTemporal.js\";\n\nimport \"./gui/EFConfiguration.ts\";\n\nexport { EFConfiguration } from \"./gui/EFConfiguration.ts\";\n\nimport \"./gui/EFWorkbench.js\";\n\nexport { EFWorkbench } from \"./gui/EFWorkbench.js\";\n\nimport \"./gui/EFPreview.js\";\n\nexport { EFPreview } from \"./gui/EFPreview.js\";\n\nimport \"./gui/EFFilmstrip.js\";\n\nexport { EFFilmstrip } from \"./gui/EFFilmstrip.js\";\n\nimport \"./gui/hierarchy/EFHierarchy.js\";\nimport \"./gui/hierarchy/EFHierarchyItem.js\";\n\nexport { EFHierarchy } from \"./gui/hierarchy/EFHierarchy.js\";\nexport {\n EFHierarchyItem,\n EFTimegroupHierarchyItem,\n EFAudioHierarchyItem,\n EFVideoHierarchyItem,\n EFCaptionsHierarchyItem,\n EFCaptionsActiveWordHierarchyItem,\n EFTextHierarchyItem,\n EFTextSegmentHierarchyItem,\n EFWaveformHierarchyItem,\n EFImageHierarchyItem,\n EFHTMLHierarchyItem,\n} from \"./gui/hierarchy/EFHierarchyItem.js\";\nexport type {\n HierarchyState,\n HierarchyActions,\n HierarchyContext,\n} from \"./gui/hierarchy/hierarchyContext.js\";\nexport { hierarchyContext } from \"./gui/hierarchy/hierarchyContext.js\";\n\n// Generic tree component\nimport \"./gui/tree/EFTree.js\";\nimport \"./gui/tree/EFTreeItem.js\";\n\nexport { EFTree } from \"./gui/tree/EFTree.js\";\nexport { EFTreeItem } from \"./gui/tree/EFTreeItem.js\";\nexport type {\n TreeItem,\n TreeState,\n TreeActions,\n TreeContext,\n} from \"./gui/tree/treeContext.js\";\nexport { treeContext, collectAllIds } from \"./gui/tree/treeContext.js\";\n\nimport \"./gui/EFTogglePlay.js\";\n\nexport { EFTogglePlay } from \"./gui/EFTogglePlay.js\";\n\nimport \"./gui/EFPlay.js\";\n\nexport { EFPlay } from \"./gui/EFPlay.js\";\n\nimport \"./gui/EFPause.js\";\n\nexport { EFPause } from \"./gui/EFPause.js\";\n\nimport \"./gui/EFToggleLoop.js\";\n\nexport { EFToggleLoop } from \"./gui/EFToggleLoop.js\";\n\nimport \"./gui/EFScrubber.js\";\n\nexport { EFScrubber } from \"./gui/EFScrubber.js\";\n\nimport \"./gui/EFTimeDisplay.js\";\n\nexport { EFTimeDisplay } from \"./gui/EFTimeDisplay.js\";\n\nimport \"./gui/EFActiveRootTemporal.js\";\n\nexport { EFActiveRootTemporal } from \"./gui/EFActiveRootTemporal.js\";\n\nimport \"./gui/EFDial.js\";\n\nexport { type DialChangeDetail, EFDial } from \"./gui/EFDial.js\";\n\nimport \"./gui/EFControls.js\";\n\nexport { EFControls } from \"./gui/EFControls.js\";\n\nimport \"./gui/EFFocusOverlay.js\";\n\nexport { EFFocusOverlay } from \"./gui/EFFocusOverlay.js\";\n\nimport \"./gui/transformUtils.js\";\n\nexport {\n getCornerPoint,\n getOppositeCorner,\n rotatePoint,\n} from \"./gui/transformUtils.js\";\n\nimport \"./gui/EFTransformHandles.ts\";\n\nexport {\n type TransformBounds,\n EFTransformHandles,\n} from \"./gui/EFTransformHandles.ts\";\n\nimport \"./gui/EFResizableBox.ts\";\n\nexport { type BoxBounds, EFResizableBox } from \"./gui/EFResizableBox.ts\";\n\nimport \"./gui/EFFitScale.js\";\n\nexport { EFFitScale } from \"./gui/EFFitScale.js\";\n\nimport \"./elements/EFSurface.ts\";\n\nexport { EFSurface } from \"./elements/EFSurface.ts\";\n\nimport \"./elements/EFThumbnailStrip.ts\";\n\nexport { EFThumbnailStrip } from \"./elements/EFThumbnailStrip.ts\";\n\nimport \"./elements/EFPanZoom.js\";\n\nexport { EFPanZoom } from \"./elements/EFPanZoom.js\";\nexport type { PanZoomTransform } from \"./elements/EFPanZoom.js\";\n\nimport \"./canvas/EFCanvas.js\";\nimport \"./canvas/EFCanvasItem.js\";\n\nexport { EFCanvas } from \"./canvas/EFCanvas.js\";\nexport { EFCanvasItem } from \"./canvas/EFCanvasItem.js\";\nexport { CanvasAPI } from \"./canvas/api/CanvasAPI.js\";\nexport type {\n CanvasElementData,\n SelectionState,\n CanvasElementBounds,\n} from \"./canvas/api/types.js\";\nexport { SelectionModel } from \"./canvas/selection/SelectionModel.js\";\n\nimport \"./gui/EFOverlayLayer.ts\";\n\nexport { EFOverlayLayer } from \"./gui/EFOverlayLayer.ts\";\n\nimport \"./gui/EFOverlayItem.ts\";\n\nexport { EFOverlayItem } from \"./gui/EFOverlayItem.ts\";\nexport type { OverlayItemPosition } from \"./gui/EFOverlayItem.ts\";\n\nimport \"./gui/EFTimelineRuler.ts\";\n\nexport {\n EFTimelineRuler,\n quantizeToFrameTimeMs,\n calculateFrameIntervalMs,\n calculatePixelsPerFrame,\n shouldShowFrameMarkers,\n} from \"./gui/EFTimelineRuler.ts\";\n\nimport \"./gui/timeline/EFTimeline.js\";\nimport \"./gui/timeline/TrimHandles.js\";\n\nexport { EFTimeline } from \"./gui/timeline/EFTimeline.js\";\nexport {\n EFTrimHandles,\n type TrimChangeDetail,\n} from \"./gui/timeline/TrimHandles.js\";\n\nif (typeof window !== \"undefined\") {\n // @ts-expect-error\n window.EF_REGISTERED = true;\n}\n\nimport \"./EF_FRAMEGEN.js\";\n\nexport { getRenderInfo, RenderInfo } from \"./getRenderInfo.js\";\nexport type { TraceContext } from \"./otel/tracingHelpers.js\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import \"./elements/EFTimegroup.js\";\nimport \"./sandbox/index.js\";\n\nexport { EFTimegroup } from \"./elements/EFTimegroup.js\";\nexport type { ContainerInfo } from \"./elements/ContainerInfo.js\";\nexport { getContainerInfoFromElement } from \"./elements/ContainerInfo.js\";\nexport type { ElementPositionInfo } from \"./elements/ElementPositionInfo.js\";\nexport {\n getPositionInfoFromElement,\n PositionInfoMixin,\n} from \"./elements/ElementPositionInfo.js\";\nexport { needsFitScale, elementNeedsFitScale } from \"./gui/FitScaleHelpers.js\";\n\nimport \"./elements/EFImage.js\";\n\nexport { EFImage } from \"./elements/EFImage.js\";\n\nimport \"./elements/EFMedia.js\";\n\nexport type { EFMedia } from \"./elements/EFMedia.js\";\n\nimport \"./elements/EFAudio.js\";\n\nexport { EFAudio } from \"./elements/EFAudio.js\";\n\nimport \"./elements/EFVideo.js\";\n\nexport { EFVideo } from \"./elements/EFVideo.js\";\n\nimport \"./elements/EFCaptions.js\";\n\nexport {\n EFCaptions,\n EFCaptionsActiveWord,\n EFCaptionsAfterActiveWord,\n EFCaptionsBeforeActiveWord,\n EFCaptionsSegment,\n} from \"./elements/EFCaptions.js\";\n\nimport \"./elements/EFText.js\";\nimport \"./elements/EFTextSegment.js\";\n\nexport { EFText } from \"./elements/EFText.js\";\nexport { EFTextSegment } from \"./elements/EFTextSegment.js\";\n\nimport \"./elements/EFWaveform.js\";\n\nexport { EFWaveform } from \"./elements/EFWaveform.js\";\n\nimport \"./elements/EFTemporal.js\";\n\nexport { isEFTemporal } from \"./elements/EFTemporal.js\";\nexport type { TemporalMixinInterface } from \"./elements/EFTemporal.js\";\n\nimport \"./gui/EFConfiguration.ts\";\n\nexport { EFConfiguration } from \"./gui/EFConfiguration.ts\";\n\nimport \"./gui/EFWorkbench.js\";\n\nexport { EFWorkbench } from \"./gui/EFWorkbench.js\";\n\nimport \"./gui/EFPreview.js\";\n\nexport { EFPreview } from \"./gui/EFPreview.js\";\n\nimport \"./gui/EFFilmstrip.js\";\n\nexport { EFFilmstrip } from \"./gui/EFFilmstrip.js\";\n\nimport \"./gui/hierarchy/EFHierarchy.js\";\nimport \"./gui/hierarchy/EFHierarchyItem.js\";\n\nexport { EFHierarchy } from \"./gui/hierarchy/EFHierarchy.js\";\nexport {\n EFHierarchyItem,\n EFTimegroupHierarchyItem,\n EFAudioHierarchyItem,\n EFVideoHierarchyItem,\n EFCaptionsHierarchyItem,\n EFCaptionsActiveWordHierarchyItem,\n EFTextHierarchyItem,\n EFTextSegmentHierarchyItem,\n EFWaveformHierarchyItem,\n EFImageHierarchyItem,\n EFHTMLHierarchyItem,\n} from \"./gui/hierarchy/EFHierarchyItem.js\";\nexport type {\n HierarchyState,\n HierarchyActions,\n HierarchyContext,\n} from \"./gui/hierarchy/hierarchyContext.js\";\nexport { hierarchyContext } from \"./gui/hierarchy/hierarchyContext.js\";\n\n// Generic tree component\nimport \"./gui/tree/EFTree.js\";\nimport \"./gui/tree/EFTreeItem.js\";\n\nexport { EFTree } from \"./gui/tree/EFTree.js\";\nexport { EFTreeItem } from \"./gui/tree/EFTreeItem.js\";\nexport type {\n TreeItem,\n TreeState,\n TreeActions,\n TreeContext,\n} from \"./gui/tree/treeContext.js\";\nexport { treeContext, collectAllIds } from \"./gui/tree/treeContext.js\";\n\nimport \"./gui/EFTogglePlay.js\";\n\nexport { EFTogglePlay } from \"./gui/EFTogglePlay.js\";\n\nimport \"./gui/EFPlay.js\";\n\nexport { EFPlay } from \"./gui/EFPlay.js\";\n\nimport \"./gui/EFPause.js\";\n\nexport { EFPause } from \"./gui/EFPause.js\";\n\nimport \"./gui/EFToggleLoop.js\";\n\nexport { EFToggleLoop } from \"./gui/EFToggleLoop.js\";\n\nimport \"./gui/EFScrubber.js\";\n\nexport { EFScrubber } from \"./gui/EFScrubber.js\";\n\nimport \"./gui/EFTimeDisplay.js\";\n\nexport { EFTimeDisplay } from \"./gui/EFTimeDisplay.js\";\n\nimport \"./gui/EFActiveRootTemporal.js\";\n\nexport { EFActiveRootTemporal } from \"./gui/EFActiveRootTemporal.js\";\n\nimport \"./gui/EFDial.js\";\n\nexport { type DialChangeDetail, EFDial } from \"./gui/EFDial.js\";\n\nimport \"./gui/EFControls.js\";\n\nexport { EFControls } from \"./gui/EFControls.js\";\n\nimport \"./gui/EFFocusOverlay.js\";\n\nexport { EFFocusOverlay } from \"./gui/EFFocusOverlay.js\";\n\nimport \"./gui/transformUtils.js\";\n\nexport {\n getCornerPoint,\n getOppositeCorner,\n rotatePoint,\n} from \"./gui/transformUtils.js\";\n\nimport \"./gui/EFTransformHandles.ts\";\n\nexport {\n type TransformBounds,\n EFTransformHandles,\n} from \"./gui/EFTransformHandles.ts\";\n\nimport \"./gui/EFResizableBox.ts\";\n\nexport { type BoxBounds, EFResizableBox } from \"./gui/EFResizableBox.ts\";\n\nimport \"./gui/EFFitScale.js\";\n\nexport { EFFitScale } from \"./gui/EFFitScale.js\";\n\nimport \"./elements/EFSurface.ts\";\n\nexport { EFSurface } from \"./elements/EFSurface.ts\";\n\nimport \"./elements/EFThumbnailStrip.ts\";\n\nexport { EFThumbnailStrip } from \"./elements/EFThumbnailStrip.ts\";\n\nimport \"./elements/EFPanZoom.js\";\n\nexport { EFPanZoom } from \"./elements/EFPanZoom.js\";\nexport type { PanZoomTransform } from \"./elements/EFPanZoom.js\";\n\nimport \"./canvas/EFCanvas.js\";\nimport \"./canvas/EFCanvasItem.js\";\n\nexport { EFCanvas } from \"./canvas/EFCanvas.js\";\nexport { EFCanvasItem } from \"./canvas/EFCanvasItem.js\";\nexport { CanvasAPI } from \"./canvas/api/CanvasAPI.js\";\nexport type {\n CanvasElementData,\n SelectionState,\n CanvasElementBounds,\n} from \"./canvas/api/types.js\";\nexport { SelectionModel } from \"./canvas/selection/SelectionModel.js\";\n\nimport \"./gui/EFOverlayLayer.ts\";\n\nexport { EFOverlayLayer } from \"./gui/EFOverlayLayer.ts\";\n\nimport \"./gui/EFOverlayItem.ts\";\n\nexport { EFOverlayItem } from \"./gui/EFOverlayItem.ts\";\nexport type { OverlayItemPosition } from \"./gui/EFOverlayItem.ts\";\n\nimport \"./gui/EFTimelineRuler.ts\";\n\nexport {\n EFTimelineRuler,\n quantizeToFrameTimeMs,\n calculateFrameIntervalMs,\n calculatePixelsPerFrame,\n shouldShowFrameMarkers,\n} from \"./gui/EFTimelineRuler.ts\";\n\nimport \"./gui/timeline/EFTimeline.js\";\nimport \"./gui/timeline/TrimHandles.js\";\n\nexport { EFTimeline } from \"./gui/timeline/EFTimeline.js\";\nexport {\n EFTrimHandles,\n type TrimChangeDetail,\n} from \"./gui/timeline/TrimHandles.js\";\n\nif (typeof window !== \"undefined\") {\n // @ts-expect-error\n window.EF_REGISTERED = true;\n}\n\nimport \"./EF_FRAMEGEN.js\";\n\n// Initialize render API\nimport \"./render/EFRenderAPI.js\";\n\nexport { getRenderInfo, RenderInfo } from \"./getRenderInfo.js\";\nexport { getRenderData } from \"./render/getRenderData.js\";\nexport type {\n RenderToVideoOptions,\n RenderProgress,\n} from \"./preview/renderTimegroupToVideo.js\";\nexport type { TraceContext } from \"./otel/tracingHelpers.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiOA,IAAI,OAAO,WAAW,YAEpB,QAAO,gBAAgB"}
|
|
@@ -48,8 +48,13 @@ function setPreviewPresentationMode(mode) {
|
|
|
48
48
|
/**
|
|
49
49
|
* Get the current render mode for HTML-to-canvas capture.
|
|
50
50
|
* Defaults to "native" if available, otherwise "foreignObject".
|
|
51
|
+
*
|
|
52
|
+
* Checks EF_NATIVE_RENDER URL parameter to force native mode when set.
|
|
51
53
|
*/
|
|
52
54
|
function getRenderMode() {
|
|
55
|
+
try {
|
|
56
|
+
if (new URLSearchParams(window.location.search).get("EF_NATIVE_RENDER") === "1") return isNativeCanvasApiAvailable() ? "native" : "foreignObject";
|
|
57
|
+
} catch {}
|
|
53
58
|
try {
|
|
54
59
|
const stored = localStorage.getItem(STORAGE_KEY_RENDER_MODE);
|
|
55
60
|
if (stored === "foreignObject" || stored === "native") return stored;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"previewSettings.js","names":["_nativeApiAvailable: boolean | null","VALID_NUMERIC_SCALES: number[]"],"sources":["../../src/preview/previewSettings.ts"],"sourcesContent":["/**\n * Preview settings module with localStorage persistence.\n * Manages configuration for the preview rendering system.\n */\n\nconst STORAGE_KEY_NATIVE_CANVAS_API = \"ef-preview-native-canvas-api-enabled\";\nconst STORAGE_KEY_PRESENTATION_MODE = \"ef-preview-presentation-mode\";\nconst STORAGE_KEY_RENDER_MODE = \"ef-preview-render-mode\";\nconst STORAGE_KEY_RESOLUTION_SCALE = \"ef-preview-resolution-scale\";\nconst STORAGE_KEY_SHOW_STATS = \"ef-preview-show-stats\";\n\n/**\n * Render mode for HTML-to-canvas capture operations.\n * - \"foreignObject\": SVG foreignObject serialization (fallback, works everywhere)\n * - \"native\": Chrome's experimental drawElementImage API (fastest when available)\n */\nexport type RenderMode = \"foreignObject\" | \"native\";\n\n/**\n * Preview resolution scale factor.\n * Controls how much to reduce the preview render resolution for better performance.\n * - 1: Full resolution (default)\n * - 0.75: 3/4 resolution\n * - 0.5: Half resolution\n * - 0.25: Quarter resolution\n * - \"auto\": Adaptive resolution that scales down during motion to prevent dropped frames,\n * and renders at full resolution when at rest\n */\nexport type PreviewResolutionScale = 1 | 0.75 | 0.5 | 0.25 | \"auto\";\n\n/**\n * Preview presentation mode determines how content is rendered in the workbench.\n * - \"clone\": Show a clone with computed styles applied (alias for \"computed\")\n * - \"dom\": Show the original DOM content directly (alias for \"original\")\n * - \"original\": Show the original DOM content directly\n * - \"computed\": Show a clone with computed styles applied\n * - \"canvas\": Render to canvas using the active rendering path\n */\nexport type PreviewPresentationMode = \"original\" | \"computed\" | \"canvas\" | \"clone\" | \"dom\";\n\n/**\n * Cached detection result for native HTML-in-Canvas API availability.\n * This is separate from the user preference - it detects browser capability.\n */\nlet _nativeApiAvailable: boolean | null = null;\n\n/**\n * Detect if the native HTML-in-Canvas API (drawElementImage) is available in this browser.\n * This checks browser capability, not user preference.\n * \n * The API is available in Chrome Canary with chrome://flags/#canvas-draw-element\n * @see https://github.com/WICG/html-in-canvas\n */\nexport function isNativeCanvasApiAvailable(): boolean {\n if (_nativeApiAvailable === null) {\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n _nativeApiAvailable = ctx !== null && \"drawElementImage\" in ctx;\n }\n return _nativeApiAvailable;\n}\n\n/**\n * Check if the native Canvas API is enabled by the user.\n * Returns true only if:\n * 1. The API is available in the browser\n * 2. The user has not explicitly disabled it\n * \n * Default is enabled when available (opt-out model).\n */\nexport function isNativeCanvasApiEnabled(): boolean {\n if (!isNativeCanvasApiAvailable()) {\n return false;\n }\n \n try {\n const stored = localStorage.getItem(STORAGE_KEY_NATIVE_CANVAS_API);\n // Default to true (enabled) when available, unless explicitly disabled\n if (stored === null) {\n return true;\n }\n return stored === \"true\";\n } catch {\n // localStorage not available (e.g., private browsing)\n return true;\n }\n}\n\n/**\n * Set whether the native Canvas API should be used (when available).\n * Persists to localStorage and dispatches a change event.\n */\nexport function setNativeCanvasApiEnabled(enabled: boolean): void {\n try {\n localStorage.setItem(STORAGE_KEY_NATIVE_CANVAS_API, String(enabled));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { nativeCanvasApiEnabled: enabled }\n }));\n}\n\n/**\n * Get the current raw user preference (ignoring availability).\n * Returns null if no preference is set.\n */\nexport function getNativeCanvasApiPreference(): boolean | null {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_NATIVE_CANVAS_API);\n if (stored === null) {\n return null;\n }\n return stored === \"true\";\n } catch {\n return null;\n }\n}\n\n/**\n * Subscribe to preview settings changes.\n * @returns Unsubscribe function\n */\nexport function onPreviewSettingsChanged(\n callback: (detail: PreviewSettingsChangedDetail) => void\n): () => void {\n const handler = (event: Event) => {\n callback((event as CustomEvent).detail);\n };\n window.addEventListener(\"ef-preview-settings-changed\", handler);\n return () => window.removeEventListener(\"ef-preview-settings-changed\", handler);\n}\n\n/**\n * Detail object for preview settings change events.\n */\nexport interface PreviewSettingsChangedDetail {\n nativeCanvasApiEnabled?: boolean;\n presentationMode?: PreviewPresentationMode;\n renderMode?: RenderMode;\n resolutionScale?: PreviewResolutionScale;\n showStats?: boolean;\n}\n\n/**\n * Get the current preview presentation mode.\n * Defaults to \"original\" if not set.\n */\nexport function getPreviewPresentationMode(): PreviewPresentationMode {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_PRESENTATION_MODE);\n if (stored === \"original\" || stored === \"computed\" || stored === \"canvas\") {\n return stored;\n }\n return \"original\";\n } catch {\n return \"original\";\n }\n}\n\n/**\n * Set the preview presentation mode.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setPreviewPresentationMode(mode: PreviewPresentationMode): void {\n try {\n localStorage.setItem(STORAGE_KEY_PRESENTATION_MODE, mode);\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { presentationMode: mode }\n }));\n}\n\n/**\n * Get the current render mode for HTML-to-canvas capture.\n * Defaults to \"native\" if available, otherwise \"foreignObject\".\n */\nexport function getRenderMode(): RenderMode {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_RENDER_MODE);\n if (stored === \"foreignObject\" || stored === \"native\") {\n return stored;\n }\n // Default: prefer native if available, otherwise foreignObject\n return isNativeCanvasApiAvailable() ? \"native\" : \"foreignObject\";\n } catch {\n return isNativeCanvasApiAvailable() ? \"native\" : \"foreignObject\";\n }\n}\n\n/**\n * Set the render mode for HTML-to-canvas capture.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setRenderMode(mode: RenderMode): void {\n try {\n localStorage.setItem(STORAGE_KEY_RENDER_MODE, mode);\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { renderMode: mode }\n }));\n}\n\n/**\n * Valid numeric resolution scale values.\n */\nconst VALID_NUMERIC_SCALES: number[] = [1, 0.75, 0.5, 0.25];\n\n/**\n * Get the current preview resolution scale.\n * Defaults to 1 (full resolution) if not set.\n */\nexport function getPreviewResolutionScale(): PreviewResolutionScale {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_RESOLUTION_SCALE);\n if (stored !== null) {\n // Check for \"auto\" string first\n if (stored === \"auto\") {\n return \"auto\";\n }\n // Then check numeric values\n const parsed = parseFloat(stored);\n if (VALID_NUMERIC_SCALES.includes(parsed)) {\n return parsed as PreviewResolutionScale;\n }\n }\n return 1;\n } catch {\n return 1;\n }\n}\n\n/**\n * Set the preview resolution scale.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setPreviewResolutionScale(scale: PreviewResolutionScale): void {\n try {\n localStorage.setItem(STORAGE_KEY_RESOLUTION_SCALE, String(scale));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { resolutionScale: scale }\n }));\n}\n\n/**\n * Get whether performance stats should be shown.\n * Defaults to false (stats hidden by default).\n */\nexport function getShowStats(): boolean {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_SHOW_STATS);\n return stored === \"true\";\n } catch {\n return false;\n }\n}\n\n/**\n * Set whether performance stats should be shown.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setShowStats(enabled: boolean): void {\n try {\n localStorage.setItem(STORAGE_KEY_SHOW_STATS, String(enabled));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { showStats: enabled }\n }));\n}\n\n"],"mappings":";AAMA,MAAM,gCAAgC;AACtC,MAAM,0BAA0B;AAChC,MAAM,+BAA+B;AACrC,MAAM,yBAAyB;;;;;AAmC/B,IAAIA,sBAAsC;;;;;;;;AAS1C,SAAgB,6BAAsC;AACpD,KAAI,wBAAwB,MAAM;EAEhC,MAAM,MADS,SAAS,cAAc,SAAS,CAC5B,WAAW,KAAK;AACnC,wBAAsB,QAAQ,QAAQ,sBAAsB;;AAE9D,QAAO;;;;;;AA2FT,SAAgB,6BAAsD;AACpE,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,8BAA8B;AAClE,MAAI,WAAW,cAAc,WAAW,cAAc,WAAW,SAC/D,QAAO;AAET,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,SAAgB,2BAA2B,MAAqC;AAC9E,KAAI;AACF,eAAa,QAAQ,+BAA+B,KAAK;SACnD;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,kBAAkB,MAAM,EACnC,CAAC,CAAC;;;;;;AAOL,SAAgB,gBAA4B;AAC1C,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,wBAAwB;AAC5D,MAAI,WAAW,mBAAmB,WAAW,SAC3C,QAAO;AAGT,SAAO,4BAA4B,GAAG,WAAW;SAC3C;AACN,SAAO,4BAA4B,GAAG,WAAW;;;;;;;AAQrD,SAAgB,cAAc,MAAwB;AACpD,KAAI;AACF,eAAa,QAAQ,yBAAyB,KAAK;SAC7C;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,YAAY,MAAM,EAC7B,CAAC,CAAC;;;;;AAML,MAAMC,uBAAiC;CAAC;CAAG;CAAM;CAAK;CAAK;;;;;AAM3D,SAAgB,4BAAoD;AAClE,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,6BAA6B;AACjE,MAAI,WAAW,MAAM;AAEnB,OAAI,WAAW,OACb,QAAO;GAGT,MAAM,SAAS,WAAW,OAAO;AACjC,OAAI,qBAAqB,SAAS,OAAO,CACvC,QAAO;;AAGX,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,SAAgB,0BAA0B,OAAqC;AAC7E,KAAI;AACF,eAAa,QAAQ,8BAA8B,OAAO,MAAM,CAAC;SAC3D;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,iBAAiB,OAAO,EACnC,CAAC,CAAC;;;;;;AAOL,SAAgB,eAAwB;AACtC,KAAI;AAEF,SADe,aAAa,QAAQ,uBAAuB,KACzC;SACZ;AACN,SAAO;;;;;;;AAQX,SAAgB,aAAa,SAAwB;AACnD,KAAI;AACF,eAAa,QAAQ,wBAAwB,OAAO,QAAQ,CAAC;SACvD;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,WAAW,SAAS,EAC/B,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"previewSettings.js","names":["_nativeApiAvailable: boolean | null","VALID_NUMERIC_SCALES: number[]"],"sources":["../../src/preview/previewSettings.ts"],"sourcesContent":["/**\n * Preview settings module with localStorage persistence.\n * Manages configuration for the preview rendering system.\n */\n\nconst STORAGE_KEY_NATIVE_CANVAS_API = \"ef-preview-native-canvas-api-enabled\";\nconst STORAGE_KEY_PRESENTATION_MODE = \"ef-preview-presentation-mode\";\nconst STORAGE_KEY_RENDER_MODE = \"ef-preview-render-mode\";\nconst STORAGE_KEY_RESOLUTION_SCALE = \"ef-preview-resolution-scale\";\nconst STORAGE_KEY_SHOW_STATS = \"ef-preview-show-stats\";\n\n/**\n * Render mode for HTML-to-canvas capture operations.\n * - \"foreignObject\": SVG foreignObject serialization (fallback, works everywhere)\n * - \"native\": Chrome's experimental drawElementImage API (fastest when available)\n */\nexport type RenderMode = \"foreignObject\" | \"native\";\n\n/**\n * Preview resolution scale factor.\n * Controls how much to reduce the preview render resolution for better performance.\n * - 1: Full resolution (default)\n * - 0.75: 3/4 resolution\n * - 0.5: Half resolution\n * - 0.25: Quarter resolution\n * - \"auto\": Adaptive resolution that scales down during motion to prevent dropped frames,\n * and renders at full resolution when at rest\n */\nexport type PreviewResolutionScale = 1 | 0.75 | 0.5 | 0.25 | \"auto\";\n\n/**\n * Preview presentation mode determines how content is rendered in the workbench.\n * - \"clone\": Show a clone with computed styles applied (alias for \"computed\")\n * - \"dom\": Show the original DOM content directly (alias for \"original\")\n * - \"original\": Show the original DOM content directly\n * - \"computed\": Show a clone with computed styles applied\n * - \"canvas\": Render to canvas using the active rendering path\n */\nexport type PreviewPresentationMode = \"original\" | \"computed\" | \"canvas\" | \"clone\" | \"dom\";\n\n/**\n * Cached detection result for native HTML-in-Canvas API availability.\n * This is separate from the user preference - it detects browser capability.\n */\nlet _nativeApiAvailable: boolean | null = null;\n\n/**\n * Detect if the native HTML-in-Canvas API (drawElementImage) is available in this browser.\n * This checks browser capability, not user preference.\n * \n * The API is available in Chrome Canary with chrome://flags/#canvas-draw-element\n * @see https://github.com/WICG/html-in-canvas\n */\nexport function isNativeCanvasApiAvailable(): boolean {\n if (_nativeApiAvailable === null) {\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n _nativeApiAvailable = ctx !== null && \"drawElementImage\" in ctx;\n }\n return _nativeApiAvailable;\n}\n\n/**\n * Check if the native Canvas API is enabled by the user.\n * Returns true only if:\n * 1. The API is available in the browser\n * 2. The user has not explicitly disabled it\n * \n * Default is enabled when available (opt-out model).\n */\nexport function isNativeCanvasApiEnabled(): boolean {\n if (!isNativeCanvasApiAvailable()) {\n return false;\n }\n \n try {\n const stored = localStorage.getItem(STORAGE_KEY_NATIVE_CANVAS_API);\n // Default to true (enabled) when available, unless explicitly disabled\n if (stored === null) {\n return true;\n }\n return stored === \"true\";\n } catch {\n // localStorage not available (e.g., private browsing)\n return true;\n }\n}\n\n/**\n * Set whether the native Canvas API should be used (when available).\n * Persists to localStorage and dispatches a change event.\n */\nexport function setNativeCanvasApiEnabled(enabled: boolean): void {\n try {\n localStorage.setItem(STORAGE_KEY_NATIVE_CANVAS_API, String(enabled));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { nativeCanvasApiEnabled: enabled }\n }));\n}\n\n/**\n * Get the current raw user preference (ignoring availability).\n * Returns null if no preference is set.\n */\nexport function getNativeCanvasApiPreference(): boolean | null {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_NATIVE_CANVAS_API);\n if (stored === null) {\n return null;\n }\n return stored === \"true\";\n } catch {\n return null;\n }\n}\n\n/**\n * Subscribe to preview settings changes.\n * @returns Unsubscribe function\n */\nexport function onPreviewSettingsChanged(\n callback: (detail: PreviewSettingsChangedDetail) => void\n): () => void {\n const handler = (event: Event) => {\n callback((event as CustomEvent).detail);\n };\n window.addEventListener(\"ef-preview-settings-changed\", handler);\n return () => window.removeEventListener(\"ef-preview-settings-changed\", handler);\n}\n\n/**\n * Detail object for preview settings change events.\n */\nexport interface PreviewSettingsChangedDetail {\n nativeCanvasApiEnabled?: boolean;\n presentationMode?: PreviewPresentationMode;\n renderMode?: RenderMode;\n resolutionScale?: PreviewResolutionScale;\n showStats?: boolean;\n}\n\n/**\n * Get the current preview presentation mode.\n * Defaults to \"original\" if not set.\n */\nexport function getPreviewPresentationMode(): PreviewPresentationMode {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_PRESENTATION_MODE);\n if (stored === \"original\" || stored === \"computed\" || stored === \"canvas\") {\n return stored;\n }\n return \"original\";\n } catch {\n return \"original\";\n }\n}\n\n/**\n * Set the preview presentation mode.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setPreviewPresentationMode(mode: PreviewPresentationMode): void {\n try {\n localStorage.setItem(STORAGE_KEY_PRESENTATION_MODE, mode);\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { presentationMode: mode }\n }));\n}\n\n/**\n * Get the current render mode for HTML-to-canvas capture.\n * Defaults to \"native\" if available, otherwise \"foreignObject\".\n * \n * Checks EF_NATIVE_RENDER URL parameter to force native mode when set.\n */\nexport function getRenderMode(): RenderMode {\n // Check URL parameter first (CLI flag override)\n try {\n const urlParams = new URLSearchParams(window.location.search);\n if (urlParams.get(\"EF_NATIVE_RENDER\") === \"1\") {\n // Force native mode if available, otherwise fall back to foreignObject\n return isNativeCanvasApiAvailable() ? \"native\" : \"foreignObject\";\n }\n } catch {\n // URL parsing failed, continue with normal logic\n }\n\n try {\n const stored = localStorage.getItem(STORAGE_KEY_RENDER_MODE);\n if (stored === \"foreignObject\" || stored === \"native\") {\n return stored;\n }\n // Default: prefer native if available, otherwise foreignObject\n return isNativeCanvasApiAvailable() ? \"native\" : \"foreignObject\";\n } catch {\n return isNativeCanvasApiAvailable() ? \"native\" : \"foreignObject\";\n }\n}\n\n/**\n * Set the render mode for HTML-to-canvas capture.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setRenderMode(mode: RenderMode): void {\n try {\n localStorage.setItem(STORAGE_KEY_RENDER_MODE, mode);\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { renderMode: mode }\n }));\n}\n\n/**\n * Valid numeric resolution scale values.\n */\nconst VALID_NUMERIC_SCALES: number[] = [1, 0.75, 0.5, 0.25];\n\n/**\n * Get the current preview resolution scale.\n * Defaults to 1 (full resolution) if not set.\n */\nexport function getPreviewResolutionScale(): PreviewResolutionScale {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_RESOLUTION_SCALE);\n if (stored !== null) {\n // Check for \"auto\" string first\n if (stored === \"auto\") {\n return \"auto\";\n }\n // Then check numeric values\n const parsed = parseFloat(stored);\n if (VALID_NUMERIC_SCALES.includes(parsed)) {\n return parsed as PreviewResolutionScale;\n }\n }\n return 1;\n } catch {\n return 1;\n }\n}\n\n/**\n * Set the preview resolution scale.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setPreviewResolutionScale(scale: PreviewResolutionScale): void {\n try {\n localStorage.setItem(STORAGE_KEY_RESOLUTION_SCALE, String(scale));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { resolutionScale: scale }\n }));\n}\n\n/**\n * Get whether performance stats should be shown.\n * Defaults to false (stats hidden by default).\n */\nexport function getShowStats(): boolean {\n try {\n const stored = localStorage.getItem(STORAGE_KEY_SHOW_STATS);\n return stored === \"true\";\n } catch {\n return false;\n }\n}\n\n/**\n * Set whether performance stats should be shown.\n * Persists to localStorage and dispatches a change event.\n */\nexport function setShowStats(enabled: boolean): void {\n try {\n localStorage.setItem(STORAGE_KEY_SHOW_STATS, String(enabled));\n } catch {\n // localStorage not available\n }\n \n // Dispatch event so components can react to the change\n window.dispatchEvent(new CustomEvent(\"ef-preview-settings-changed\", {\n detail: { showStats: enabled }\n }));\n}\n\n"],"mappings":";AAMA,MAAM,gCAAgC;AACtC,MAAM,0BAA0B;AAChC,MAAM,+BAA+B;AACrC,MAAM,yBAAyB;;;;;AAmC/B,IAAIA,sBAAsC;;;;;;;;AAS1C,SAAgB,6BAAsC;AACpD,KAAI,wBAAwB,MAAM;EAEhC,MAAM,MADS,SAAS,cAAc,SAAS,CAC5B,WAAW,KAAK;AACnC,wBAAsB,QAAQ,QAAQ,sBAAsB;;AAE9D,QAAO;;;;;;AA2FT,SAAgB,6BAAsD;AACpE,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,8BAA8B;AAClE,MAAI,WAAW,cAAc,WAAW,cAAc,WAAW,SAC/D,QAAO;AAET,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,SAAgB,2BAA2B,MAAqC;AAC9E,KAAI;AACF,eAAa,QAAQ,+BAA+B,KAAK;SACnD;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,kBAAkB,MAAM,EACnC,CAAC,CAAC;;;;;;;;AASL,SAAgB,gBAA4B;AAE1C,KAAI;AAEF,MADkB,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAC/C,IAAI,mBAAmB,KAAK,IAExC,QAAO,4BAA4B,GAAG,WAAW;SAE7C;AAIR,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,wBAAwB;AAC5D,MAAI,WAAW,mBAAmB,WAAW,SAC3C,QAAO;AAGT,SAAO,4BAA4B,GAAG,WAAW;SAC3C;AACN,SAAO,4BAA4B,GAAG,WAAW;;;;;;;AAQrD,SAAgB,cAAc,MAAwB;AACpD,KAAI;AACF,eAAa,QAAQ,yBAAyB,KAAK;SAC7C;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,YAAY,MAAM,EAC7B,CAAC,CAAC;;;;;AAML,MAAMC,uBAAiC;CAAC;CAAG;CAAM;CAAK;CAAK;;;;;AAM3D,SAAgB,4BAAoD;AAClE,KAAI;EACF,MAAM,SAAS,aAAa,QAAQ,6BAA6B;AACjE,MAAI,WAAW,MAAM;AAEnB,OAAI,WAAW,OACb,QAAO;GAGT,MAAM,SAAS,WAAW,OAAO;AACjC,OAAI,qBAAqB,SAAS,OAAO,CACvC,QAAO;;AAGX,SAAO;SACD;AACN,SAAO;;;;;;;AAQX,SAAgB,0BAA0B,OAAqC;AAC7E,KAAI;AACF,eAAa,QAAQ,8BAA8B,OAAO,MAAM,CAAC;SAC3D;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,iBAAiB,OAAO,EACnC,CAAC,CAAC;;;;;;AAOL,SAAgB,eAAwB;AACtC,KAAI;AAEF,SADe,aAAa,QAAQ,uBAAuB,KACzC;SACZ;AACN,SAAO;;;;;;;AAQX,SAAgB,aAAa,SAAwB;AACnD,KAAI;AACF,eAAa,QAAQ,wBAAwB,OAAO,QAAQ,CAAC;SACvD;AAKR,QAAO,cAAc,IAAI,YAAY,+BAA+B,EAClE,QAAQ,EAAE,WAAW,SAAS,EAC/B,CAAC,CAAC"}
|
|
@@ -33,7 +33,8 @@ interface RenderToVideoOptions {
|
|
|
33
33
|
returnBuffer?: boolean;
|
|
34
34
|
preferredAudioCodecs?: AudioCodec[];
|
|
35
35
|
benchmarkMode?: boolean;
|
|
36
|
+
customWritableStream?: WritableStream<Uint8Array>;
|
|
36
37
|
}
|
|
37
38
|
//#endregion
|
|
38
|
-
export { RenderToVideoOptions };
|
|
39
|
+
export { RenderProgress, RenderToVideoOptions };
|
|
39
40
|
//# sourceMappingURL=renderTimegroupToVideo.d.ts.map
|
|
@@ -140,17 +140,25 @@ async function renderTimegroupToVideo(timegroup, options = {}) {
|
|
|
140
140
|
let encodingCanvas = null;
|
|
141
141
|
let encodingCtx = null;
|
|
142
142
|
if (!config.benchmarkMode) {
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
useStreaming = fileStream !== null;
|
|
146
|
-
}
|
|
147
|
-
if (useStreaming && fileStream) {
|
|
148
|
-
target = new StreamTarget(fileStream.writable);
|
|
143
|
+
if (options.customWritableStream) {
|
|
144
|
+
target = new StreamTarget(options.customWritableStream);
|
|
149
145
|
output = new Output({
|
|
150
146
|
format: new Mp4OutputFormat({ fastStart: "fragmented" }),
|
|
151
147
|
target
|
|
152
148
|
});
|
|
153
|
-
|
|
149
|
+
useStreaming = true;
|
|
150
|
+
} else if (config.streaming) {
|
|
151
|
+
fileStream = await getFileWritableStream(config.filename);
|
|
152
|
+
useStreaming = fileStream !== null;
|
|
153
|
+
if (useStreaming && fileStream) {
|
|
154
|
+
target = new StreamTarget(fileStream.writable);
|
|
155
|
+
output = new Output({
|
|
156
|
+
format: new Mp4OutputFormat({ fastStart: "fragmented" }),
|
|
157
|
+
target
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!target) {
|
|
154
162
|
target = new BufferTarget();
|
|
155
163
|
output = new Output({
|
|
156
164
|
format: new Mp4OutputFormat(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderTimegroupToVideo.js","names":["timestamps: number[]","output: Output | null","videoSource: CanvasSource | null","audioSource: AudioBufferSource | null","target: BufferTarget | StreamTarget | null","fileStream: { writable: WritableStream<Uint8Array>; close: () => Promise<void> } | null","encodingCanvas: OffscreenCanvas | null","encodingCtx: OffscreenCanvasRenderingContext2D | null","videoConfig: VideoEncodingConfig","lastFramePreviewUrl: string | undefined"],"sources":["../../src/preview/renderTimegroupToVideo.ts"],"sourcesContent":["/**\n * Video rendering for timegroups.\n * \n * Uses the EXACT same rendering path as thumbnail generation (captureFromClone),\n * ensuring consistency between preview thumbnails and exported video.\n */\n\nimport {\n Output,\n Mp4OutputFormat,\n BufferTarget,\n StreamTarget,\n CanvasSource,\n AudioBufferSource,\n QUALITY_HIGH,\n canEncodeAudio,\n getEncodableAudioCodecs,\n type VideoEncodingConfig,\n type AudioEncodingConfig,\n type AudioCodec,\n} from \"mediabunny\";\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFVideo } from \"../elements/EFVideo.js\";\nimport {\n resetRenderState,\n captureFromClone,\n type ContentReadyMode,\n} from \"./renderTimegroupToCanvas.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RenderProgress {\n progress: number;\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n estimatedRemainingMs: number;\n speedMultiplier: number;\n framePreviewUrl?: string;\n}\n\nexport interface RenderToVideoOptions {\n fps?: number;\n codec?: \"avc\" | \"hevc\" | \"vp9\" | \"av1\" | \"vp8\";\n bitrate?: number;\n filename?: string;\n scale?: number;\n keyFrameInterval?: number;\n fromMs?: number;\n toMs?: number;\n onProgress?: (progress: RenderProgress) => void;\n streaming?: boolean;\n signal?: AbortSignal;\n includeAudio?: boolean;\n audioBitrate?: number;\n contentReadyMode?: ContentReadyMode;\n blockingTimeoutMs?: number;\n returnBuffer?: boolean;\n preferredAudioCodecs?: AudioCodec[];\n benchmarkMode?: boolean;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class NoSupportedAudioCodecError extends Error {\n constructor(requestedCodecs: AudioCodec[], availableCodecs: AudioCodec[]) {\n super(\n `No supported audio codec found. Requested: [${requestedCodecs.join(\", \")}], ` +\n `Available: [${availableCodecs.length > 0 ? availableCodecs.join(\", \") : \"none\"}]`\n );\n this.name = \"NoSupportedAudioCodecError\";\n }\n}\n\nexport class RenderCancelledError extends Error {\n constructor() {\n super(\"Render cancelled\");\n this.name = \"RenderCancelledError\";\n }\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\ninterface ResolvedConfig {\n fps: number;\n codec: \"avc\" | \"hevc\" | \"vp9\" | \"av1\" | \"vp8\";\n bitrate: number;\n filename: string;\n scale: number;\n keyFrameInterval: number;\n startMs: number;\n endMs: number;\n renderDurationMs: number;\n videoWidth: number;\n videoHeight: number;\n totalFrames: number;\n frameDurationMs: number;\n frameDurationS: number;\n streaming: boolean;\n includeAudio: boolean;\n audioBitrate: number;\n contentReadyMode: ContentReadyMode;\n blockingTimeoutMs: number;\n returnBuffer: boolean;\n preferredAudioCodecs: AudioCodec[];\n benchmarkMode: boolean;\n}\n\nfunction resolveConfig(\n timegroup: EFTimegroup,\n options: RenderToVideoOptions,\n): ResolvedConfig {\n const fps = options.fps ?? timegroup.effectiveFps ?? 30;\n const codec = options.codec ?? \"avc\";\n const bitrate = options.bitrate ?? 8_000_000;\n const filename = options.filename ?? \"timegroup-video.mp4\";\n const scale = options.scale ?? 1;\n const keyFrameInterval = options.keyFrameInterval ?? 2;\n const streaming = options.streaming ?? true;\n const includeAudio = options.includeAudio ?? true;\n const audioBitrate = options.audioBitrate ?? 128_000;\n const contentReadyMode = options.contentReadyMode ?? \"blocking\";\n const blockingTimeoutMs = options.blockingTimeoutMs ?? 5000;\n const returnBuffer = options.returnBuffer ?? false;\n const preferredAudioCodecs = options.preferredAudioCodecs ?? [\"aac\", \"opus\"];\n const benchmarkMode = options.benchmarkMode ?? false;\n\n const totalDurationMs = timegroup.durationMs;\n if (!totalDurationMs || totalDurationMs <= 0) {\n throw new Error(\"Timegroup has no duration\");\n }\n\n const startMs = Math.max(0, options.fromMs ?? 0);\n const endMs = options.toMs !== undefined ? Math.min(options.toMs, totalDurationMs) : totalDurationMs;\n const renderDurationMs = endMs - startMs;\n \n if (renderDurationMs <= 0) {\n throw new Error(`Invalid render range: from ${startMs}ms to ${endMs}ms`);\n }\n\n const timegroupWidth = timegroup.offsetWidth || 1920;\n const timegroupHeight = timegroup.offsetHeight || 1080;\n const width = Math.floor(timegroupWidth * scale);\n const height = Math.floor(timegroupHeight * scale);\n\n const videoWidth = width % 2 === 0 ? width : width - 1;\n const videoHeight = height % 2 === 0 ? height : height - 1;\n\n const frameDurationMs = 1000 / fps;\n const totalFrames = Math.ceil(renderDurationMs / frameDurationMs);\n const frameDurationS = frameDurationMs / 1000;\n\n return {\n fps,\n codec,\n bitrate,\n filename,\n scale,\n keyFrameInterval,\n startMs,\n endMs,\n renderDurationMs,\n videoWidth,\n videoHeight,\n totalFrames,\n frameDurationMs,\n frameDurationS,\n streaming,\n includeAudio,\n audioBitrate,\n contentReadyMode,\n blockingTimeoutMs,\n returnBuffer,\n preferredAudioCodecs,\n benchmarkMode,\n };\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isFileSystemAccessSupported(): boolean {\n return typeof window !== \"undefined\" && \"showSaveFilePicker\" in window;\n}\n\nasync function getFileWritableStream(\n filename: string,\n): Promise<{ writable: WritableStream<Uint8Array>; close: () => Promise<void> } | null> {\n if (!isFileSystemAccessSupported()) {\n return null;\n }\n\n try {\n const fileHandle = await (window as any).showSaveFilePicker({\n suggestedName: filename,\n types: [{ description: \"MP4 Video\", accept: { \"video/mp4\": [\".mp4\"] } }],\n });\n const writable = await fileHandle.createWritable();\n return { writable, close: async () => { await writable.close(); } };\n } catch (e) {\n if ((e as Error).name !== \"AbortError\") {\n console.warn(\"[renderToVideo] File System Access failed:\", e);\n }\n return null;\n }\n}\n\nasync function selectAudioCodec(\n preferredCodecs: AudioCodec[],\n encodingOptions: { numberOfChannels: number; sampleRate: number; bitrate: number },\n): Promise<AudioCodec> {\n for (const codec of preferredCodecs) {\n try {\n const isSupported = await canEncodeAudio(codec, encodingOptions);\n if (isSupported) return codec;\n } catch (e) {\n console.warn(`[selectAudioCodec] Check failed for ${codec}:`, e);\n }\n }\n const availableCodecs = await getEncodableAudioCodecs(undefined, encodingOptions);\n throw new NoSupportedAudioCodecError(preferredCodecs, availableCodecs);\n}\n\nfunction downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport async function getSupportedAudioCodecs(options?: {\n numberOfChannels?: number;\n sampleRate?: number;\n bitrate?: number;\n}): Promise<AudioCodec[]> {\n const { numberOfChannels = 2, sampleRate = 48000, bitrate = 128000 } = options ?? {};\n return getEncodableAudioCodecs(undefined, { numberOfChannels, sampleRate, bitrate });\n}\n\n/**\n * Renders a timegroup to an MP4 video file.\n * \n * Uses the EXACT same code path as thumbnail generation (captureFromClone).\n * This ensures consistency - if thumbnails work, video export works.\n */\nexport async function renderTimegroupToVideo(\n timegroup: EFTimegroup,\n options: RenderToVideoOptions = {},\n): Promise<Uint8Array | undefined> {\n const config = resolveConfig(timegroup, options);\n const { signal, onProgress } = options;\n \n const checkCancelled = () => {\n if (signal?.aborted) throw new RenderCancelledError();\n };\n \n resetRenderState();\n \n // =========================================================================\n // Create render clone - EXACT same as captureBatch in EFTimegroup\n // =========================================================================\n const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } =\n await timegroup.createRenderClone();\n \n // Pre-fetch main video segments for all timestamps\n // This ensures all segments are cached before rendering starts,\n // avoiding network delays during the frame loop\n const timestamps: number[] = [];\n for (let i = 0; i < config.totalFrames; i++) {\n timestamps.push(config.startMs + i * config.frameDurationMs);\n }\n \n const videoElements = renderClone.querySelectorAll(\"ef-video\");\n if (videoElements.length > 0) {\n console.log(`[renderTimegroupToVideo] Prefetching main video segments for ${videoElements.length} video(s)...`);\n await Promise.all(\n Array.from(videoElements).map((video) =>\n (video as EFVideo).prefetchMainVideoSegments(timestamps),\n ),\n );\n console.log(`[renderTimegroupToVideo] Prefetch complete`);\n }\n \n // =========================================================================\n // Set up video encoding\n // =========================================================================\n let output: Output | null = null;\n let videoSource: CanvasSource | null = null;\n let audioSource: AudioBufferSource | null = null;\n let target: BufferTarget | StreamTarget | null = null;\n let fileStream: { writable: WritableStream<Uint8Array>; close: () => Promise<void> } | null = null;\n let useStreaming = false;\n let encodingCanvas: OffscreenCanvas | null = null;\n let encodingCtx: OffscreenCanvasRenderingContext2D | null = null;\n \n if (!config.benchmarkMode) {\n if (config.streaming) {\n fileStream = await getFileWritableStream(config.filename);\n useStreaming = fileStream !== null;\n }\n \n if (useStreaming && fileStream) {\n target = new StreamTarget(fileStream.writable as any);\n output = new Output({\n format: new Mp4OutputFormat({ fastStart: \"fragmented\" }),\n target,\n });\n } else {\n target = new BufferTarget();\n output = new Output({ format: new Mp4OutputFormat(), target });\n }\n \n encodingCanvas = new OffscreenCanvas(config.videoWidth, config.videoHeight);\n encodingCtx = encodingCanvas.getContext(\"2d\");\n if (!encodingCtx) {\n cleanupRenderClone();\n throw new Error(\"Failed to get encoding canvas context\");\n }\n \n const videoConfig: VideoEncodingConfig = {\n codec: config.codec,\n bitrate: config.bitrate,\n keyFrameInterval: config.keyFrameInterval,\n };\n videoSource = new CanvasSource(encodingCanvas, videoConfig);\n output.addVideoTrack(videoSource);\n \n if (config.includeAudio) {\n const selectedCodec = await selectAudioCodec(config.preferredAudioCodecs, {\n numberOfChannels: 2,\n sampleRate: 48000,\n bitrate: config.audioBitrate,\n });\n const audioConfig: AudioEncodingConfig = {\n codec: selectedCodec,\n bitrate: config.audioBitrate,\n };\n audioSource = new AudioBufferSource(audioConfig);\n output.addAudioTrack(audioSource);\n }\n \n await output.start();\n }\n \n // =========================================================================\n // Frame loop - using EXACT same code path as captureBatch\n // =========================================================================\n const renderStartTime = performance.now();\n let lastFramePreviewUrl: string | undefined;\n let lastRenderedAudioEndMs = config.startMs;\n const audioChunkDurationMs = 2000;\n \n let totalSeekMs = 0;\n let totalCaptureMs = 0;\n let totalEncodeMs = 0;\n \n try {\n for (let frameIndex = 0; frameIndex < config.totalFrames; frameIndex++) {\n checkCancelled();\n \n const timeMs = timestamps[frameIndex]!;\n const timestampS = (frameIndex * config.frameDurationMs) / 1000;\n \n // Render audio chunk if needed\n if (audioSource && timeMs >= lastRenderedAudioEndMs + audioChunkDurationMs) {\n const chunkEndMs = Math.min(timeMs + audioChunkDurationMs, config.endMs);\n try {\n const audioBuffer = await timegroup.renderAudio(lastRenderedAudioEndMs, chunkEndMs);\n if (audioBuffer && audioBuffer.length > 0) {\n await audioSource.add(audioBuffer);\n }\n } catch (e) { /* Audio render failures are non-fatal */ }\n lastRenderedAudioEndMs = chunkEndMs;\n }\n \n // =====================================================================\n // EXACT same pattern as captureBatch: seekForRender + captureFromClone\n // =====================================================================\n const seekStart = performance.now();\n await renderClone.seekForRender(timeMs);\n totalSeekMs += performance.now() - seekStart;\n \n const captureStart = performance.now();\n const canvas = await captureFromClone(renderClone, renderContainer, {\n scale: config.scale,\n contentReadyMode: config.contentReadyMode,\n blockingTimeoutMs: config.blockingTimeoutMs,\n originalTimegroup: timegroup,\n });\n totalCaptureMs += performance.now() - captureStart;\n \n // Encode frame\n if (videoSource && output && encodingCtx) {\n const encodeStart = performance.now();\n encodingCtx.drawImage(\n canvas,\n 0, 0, canvas.width, canvas.height,\n 0, 0, config.videoWidth, config.videoHeight,\n );\n await videoSource.add(timestampS, config.frameDurationS);\n totalEncodeMs += performance.now() - encodeStart;\n }\n \n // Progress\n const currentFrame = frameIndex + 1;\n const progress = currentFrame / config.totalFrames;\n const renderedMs = currentFrame * config.frameDurationMs;\n const elapsedMs = performance.now() - renderStartTime;\n const msPerFrame = elapsedMs / currentFrame;\n const remainingFrames = config.totalFrames - currentFrame;\n const estimatedRemainingMs = remainingFrames * msPerFrame;\n const speedMultiplier = renderedMs / elapsedMs;\n \n if (onProgress && frameIndex % 10 === 0) {\n const previewWidth = 160;\n const previewHeight = Math.round(previewWidth * (config.videoHeight / config.videoWidth));\n const thumbCanvas = document.createElement(\"canvas\");\n thumbCanvas.width = previewWidth;\n thumbCanvas.height = previewHeight;\n const thumbCtx = thumbCanvas.getContext(\"2d\")!;\n thumbCtx.drawImage(canvas, 0, 0, previewWidth, previewHeight);\n lastFramePreviewUrl = thumbCanvas.toDataURL(\"image/jpeg\", 0.7);\n }\n \n onProgress?.({\n progress,\n currentFrame,\n totalFrames: config.totalFrames,\n renderedMs,\n totalDurationMs: config.renderDurationMs,\n elapsedMs,\n estimatedRemainingMs,\n speedMultiplier,\n framePreviewUrl: lastFramePreviewUrl,\n });\n }\n \n // Render remaining audio\n if (audioSource && lastRenderedAudioEndMs < config.endMs) {\n try {\n const audioBuffer = await timegroup.renderAudio(lastRenderedAudioEndMs, config.endMs);\n if (audioBuffer && audioBuffer.length > 0) {\n await audioSource.add(audioBuffer);\n }\n } catch (e) { /* Audio render failures are non-fatal */ }\n }\n \n const totalTime = performance.now() - renderStartTime;\n console.log(\n `[renderTimegroupToVideo] ${config.totalFrames} frames: ` +\n `seek=${totalSeekMs.toFixed(0)}ms, capture=${totalCaptureMs.toFixed(0)}ms, ` +\n `encode=${totalEncodeMs.toFixed(0)}ms, total=${totalTime.toFixed(0)}ms`\n );\n \n if (config.benchmarkMode) {\n return undefined;\n }\n \n await output!.finalize();\n \n if (useStreaming) {\n return undefined;\n } else {\n const bufferTarget = target as BufferTarget;\n const videoBuffer = bufferTarget.buffer;\n if (!videoBuffer) {\n throw new Error(\"Video encoding failed: no buffer produced\");\n }\n \n if (config.returnBuffer) {\n return new Uint8Array(videoBuffer);\n }\n \n const videoBlob = new Blob([videoBuffer], { type: \"video/mp4\" });\n downloadBlob(videoBlob, config.filename);\n return undefined;\n }\n \n } finally {\n cleanupRenderClone();\n }\n}\n\nexport { QUALITY_HIGH };\nexport type { AudioCodec };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"renderTimegroupToVideo.js","names":["timestamps: number[]","output: Output | null","videoSource: CanvasSource | null","audioSource: AudioBufferSource | null","target: BufferTarget | StreamTarget | null","fileStream: { writable: WritableStream<Uint8Array>; close: () => Promise<void> } | null","encodingCanvas: OffscreenCanvas | null","encodingCtx: OffscreenCanvasRenderingContext2D | null","videoConfig: VideoEncodingConfig","lastFramePreviewUrl: string | undefined"],"sources":["../../src/preview/renderTimegroupToVideo.ts"],"sourcesContent":["/**\n * Video rendering for timegroups.\n * \n * Uses the EXACT same rendering path as thumbnail generation (captureFromClone),\n * ensuring consistency between preview thumbnails and exported video.\n */\n\nimport {\n Output,\n Mp4OutputFormat,\n BufferTarget,\n StreamTarget,\n CanvasSource,\n AudioBufferSource,\n QUALITY_HIGH,\n canEncodeAudio,\n getEncodableAudioCodecs,\n type VideoEncodingConfig,\n type AudioEncodingConfig,\n type AudioCodec,\n} from \"mediabunny\";\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFVideo } from \"../elements/EFVideo.js\";\nimport {\n resetRenderState,\n captureFromClone,\n type ContentReadyMode,\n} from \"./renderTimegroupToCanvas.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RenderProgress {\n progress: number;\n currentFrame: number;\n totalFrames: number;\n renderedMs: number;\n totalDurationMs: number;\n elapsedMs: number;\n estimatedRemainingMs: number;\n speedMultiplier: number;\n framePreviewUrl?: string;\n}\n\nexport interface RenderToVideoOptions {\n fps?: number;\n codec?: \"avc\" | \"hevc\" | \"vp9\" | \"av1\" | \"vp8\";\n bitrate?: number;\n filename?: string;\n scale?: number;\n keyFrameInterval?: number;\n fromMs?: number;\n toMs?: number;\n onProgress?: (progress: RenderProgress) => void;\n streaming?: boolean;\n signal?: AbortSignal;\n includeAudio?: boolean;\n audioBitrate?: number;\n contentReadyMode?: ContentReadyMode;\n blockingTimeoutMs?: number;\n returnBuffer?: boolean;\n preferredAudioCodecs?: AudioCodec[];\n benchmarkMode?: boolean;\n customWritableStream?: WritableStream<Uint8Array>; // For programmatic streaming (CLI/Playwright)\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\nexport class NoSupportedAudioCodecError extends Error {\n constructor(requestedCodecs: AudioCodec[], availableCodecs: AudioCodec[]) {\n super(\n `No supported audio codec found. Requested: [${requestedCodecs.join(\", \")}], ` +\n `Available: [${availableCodecs.length > 0 ? availableCodecs.join(\", \") : \"none\"}]`\n );\n this.name = \"NoSupportedAudioCodecError\";\n }\n}\n\nexport class RenderCancelledError extends Error {\n constructor() {\n super(\"Render cancelled\");\n this.name = \"RenderCancelledError\";\n }\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\ninterface ResolvedConfig {\n fps: number;\n codec: \"avc\" | \"hevc\" | \"vp9\" | \"av1\" | \"vp8\";\n bitrate: number;\n filename: string;\n scale: number;\n keyFrameInterval: number;\n startMs: number;\n endMs: number;\n renderDurationMs: number;\n videoWidth: number;\n videoHeight: number;\n totalFrames: number;\n frameDurationMs: number;\n frameDurationS: number;\n streaming: boolean;\n includeAudio: boolean;\n audioBitrate: number;\n contentReadyMode: ContentReadyMode;\n blockingTimeoutMs: number;\n returnBuffer: boolean;\n preferredAudioCodecs: AudioCodec[];\n benchmarkMode: boolean;\n}\n\nfunction resolveConfig(\n timegroup: EFTimegroup,\n options: RenderToVideoOptions,\n): ResolvedConfig {\n const fps = options.fps ?? timegroup.effectiveFps ?? 30;\n const codec = options.codec ?? \"avc\";\n const bitrate = options.bitrate ?? 8_000_000;\n const filename = options.filename ?? \"timegroup-video.mp4\";\n const scale = options.scale ?? 1;\n const keyFrameInterval = options.keyFrameInterval ?? 2;\n const streaming = options.streaming ?? true;\n const includeAudio = options.includeAudio ?? true;\n const audioBitrate = options.audioBitrate ?? 128_000;\n const contentReadyMode = options.contentReadyMode ?? \"blocking\";\n const blockingTimeoutMs = options.blockingTimeoutMs ?? 5000;\n const returnBuffer = options.returnBuffer ?? false;\n const preferredAudioCodecs = options.preferredAudioCodecs ?? [\"aac\", \"opus\"];\n const benchmarkMode = options.benchmarkMode ?? false;\n\n const totalDurationMs = timegroup.durationMs;\n if (!totalDurationMs || totalDurationMs <= 0) {\n throw new Error(\"Timegroup has no duration\");\n }\n\n const startMs = Math.max(0, options.fromMs ?? 0);\n const endMs = options.toMs !== undefined ? Math.min(options.toMs, totalDurationMs) : totalDurationMs;\n const renderDurationMs = endMs - startMs;\n \n if (renderDurationMs <= 0) {\n throw new Error(`Invalid render range: from ${startMs}ms to ${endMs}ms`);\n }\n\n const timegroupWidth = timegroup.offsetWidth || 1920;\n const timegroupHeight = timegroup.offsetHeight || 1080;\n const width = Math.floor(timegroupWidth * scale);\n const height = Math.floor(timegroupHeight * scale);\n\n const videoWidth = width % 2 === 0 ? width : width - 1;\n const videoHeight = height % 2 === 0 ? height : height - 1;\n\n const frameDurationMs = 1000 / fps;\n const totalFrames = Math.ceil(renderDurationMs / frameDurationMs);\n const frameDurationS = frameDurationMs / 1000;\n\n return {\n fps,\n codec,\n bitrate,\n filename,\n scale,\n keyFrameInterval,\n startMs,\n endMs,\n renderDurationMs,\n videoWidth,\n videoHeight,\n totalFrames,\n frameDurationMs,\n frameDurationS,\n streaming,\n includeAudio,\n audioBitrate,\n contentReadyMode,\n blockingTimeoutMs,\n returnBuffer,\n preferredAudioCodecs,\n benchmarkMode,\n };\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction isFileSystemAccessSupported(): boolean {\n return typeof window !== \"undefined\" && \"showSaveFilePicker\" in window;\n}\n\nasync function getFileWritableStream(\n filename: string,\n): Promise<{ writable: WritableStream<Uint8Array>; close: () => Promise<void> } | null> {\n if (!isFileSystemAccessSupported()) {\n return null;\n }\n\n try {\n const fileHandle = await (window as any).showSaveFilePicker({\n suggestedName: filename,\n types: [{ description: \"MP4 Video\", accept: { \"video/mp4\": [\".mp4\"] } }],\n });\n const writable = await fileHandle.createWritable();\n return { writable, close: async () => { await writable.close(); } };\n } catch (e) {\n if ((e as Error).name !== \"AbortError\") {\n console.warn(\"[renderToVideo] File System Access failed:\", e);\n }\n return null;\n }\n}\n\nasync function selectAudioCodec(\n preferredCodecs: AudioCodec[],\n encodingOptions: { numberOfChannels: number; sampleRate: number; bitrate: number },\n): Promise<AudioCodec> {\n for (const codec of preferredCodecs) {\n try {\n const isSupported = await canEncodeAudio(codec, encodingOptions);\n if (isSupported) return codec;\n } catch (e) {\n console.warn(`[selectAudioCodec] Check failed for ${codec}:`, e);\n }\n }\n const availableCodecs = await getEncodableAudioCodecs(undefined, encodingOptions);\n throw new NoSupportedAudioCodecError(preferredCodecs, availableCodecs);\n}\n\nfunction downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport async function getSupportedAudioCodecs(options?: {\n numberOfChannels?: number;\n sampleRate?: number;\n bitrate?: number;\n}): Promise<AudioCodec[]> {\n const { numberOfChannels = 2, sampleRate = 48000, bitrate = 128000 } = options ?? {};\n return getEncodableAudioCodecs(undefined, { numberOfChannels, sampleRate, bitrate });\n}\n\n/**\n * Renders a timegroup to an MP4 video file.\n * \n * Uses the EXACT same code path as thumbnail generation (captureFromClone).\n * This ensures consistency - if thumbnails work, video export works.\n */\nexport async function renderTimegroupToVideo(\n timegroup: EFTimegroup,\n options: RenderToVideoOptions = {},\n): Promise<Uint8Array | undefined> {\n const config = resolveConfig(timegroup, options);\n const { signal, onProgress } = options;\n \n const checkCancelled = () => {\n if (signal?.aborted) throw new RenderCancelledError();\n };\n \n resetRenderState();\n \n // =========================================================================\n // Create render clone - EXACT same as captureBatch in EFTimegroup\n // =========================================================================\n const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } =\n await timegroup.createRenderClone();\n \n // Pre-fetch main video segments for all timestamps\n // This ensures all segments are cached before rendering starts,\n // avoiding network delays during the frame loop\n const timestamps: number[] = [];\n for (let i = 0; i < config.totalFrames; i++) {\n timestamps.push(config.startMs + i * config.frameDurationMs);\n }\n \n const videoElements = renderClone.querySelectorAll(\"ef-video\");\n if (videoElements.length > 0) {\n console.log(`[renderTimegroupToVideo] Prefetching main video segments for ${videoElements.length} video(s)...`);\n await Promise.all(\n Array.from(videoElements).map((video) =>\n (video as EFVideo).prefetchMainVideoSegments(timestamps),\n ),\n );\n console.log(`[renderTimegroupToVideo] Prefetch complete`);\n }\n \n // =========================================================================\n // Set up video encoding\n // =========================================================================\n let output: Output | null = null;\n let videoSource: CanvasSource | null = null;\n let audioSource: AudioBufferSource | null = null;\n let target: BufferTarget | StreamTarget | null = null;\n let fileStream: { writable: WritableStream<Uint8Array>; close: () => Promise<void> } | null = null;\n let useStreaming = false;\n let encodingCanvas: OffscreenCanvas | null = null;\n let encodingCtx: OffscreenCanvasRenderingContext2D | null = null;\n \n if (!config.benchmarkMode) {\n // Check for custom writable stream first (for programmatic streaming)\n if (options.customWritableStream) {\n target = new StreamTarget(options.customWritableStream as any);\n output = new Output({\n format: new Mp4OutputFormat({ fastStart: \"fragmented\" }),\n target,\n });\n useStreaming = true;\n } else if (config.streaming) {\n fileStream = await getFileWritableStream(config.filename);\n useStreaming = fileStream !== null;\n \n if (useStreaming && fileStream) {\n target = new StreamTarget(fileStream.writable as any);\n output = new Output({\n format: new Mp4OutputFormat({ fastStart: \"fragmented\" }),\n target,\n });\n }\n }\n \n if (!target) {\n target = new BufferTarget();\n output = new Output({ format: new Mp4OutputFormat(), target });\n }\n \n encodingCanvas = new OffscreenCanvas(config.videoWidth, config.videoHeight);\n encodingCtx = encodingCanvas.getContext(\"2d\");\n if (!encodingCtx) {\n cleanupRenderClone();\n throw new Error(\"Failed to get encoding canvas context\");\n }\n \n const videoConfig: VideoEncodingConfig = {\n codec: config.codec,\n bitrate: config.bitrate,\n keyFrameInterval: config.keyFrameInterval,\n };\n videoSource = new CanvasSource(encodingCanvas, videoConfig);\n output.addVideoTrack(videoSource);\n \n if (config.includeAudio) {\n const selectedCodec = await selectAudioCodec(config.preferredAudioCodecs, {\n numberOfChannels: 2,\n sampleRate: 48000,\n bitrate: config.audioBitrate,\n });\n const audioConfig: AudioEncodingConfig = {\n codec: selectedCodec,\n bitrate: config.audioBitrate,\n };\n audioSource = new AudioBufferSource(audioConfig);\n output.addAudioTrack(audioSource);\n }\n \n await output.start();\n }\n \n // =========================================================================\n // Frame loop - using EXACT same code path as captureBatch\n // =========================================================================\n const renderStartTime = performance.now();\n let lastFramePreviewUrl: string | undefined;\n let lastRenderedAudioEndMs = config.startMs;\n const audioChunkDurationMs = 2000;\n \n let totalSeekMs = 0;\n let totalCaptureMs = 0;\n let totalEncodeMs = 0;\n \n try {\n for (let frameIndex = 0; frameIndex < config.totalFrames; frameIndex++) {\n checkCancelled();\n \n const timeMs = timestamps[frameIndex]!;\n const timestampS = (frameIndex * config.frameDurationMs) / 1000;\n \n // Render audio chunk if needed\n if (audioSource && timeMs >= lastRenderedAudioEndMs + audioChunkDurationMs) {\n const chunkEndMs = Math.min(timeMs + audioChunkDurationMs, config.endMs);\n try {\n const audioBuffer = await timegroup.renderAudio(lastRenderedAudioEndMs, chunkEndMs);\n if (audioBuffer && audioBuffer.length > 0) {\n await audioSource.add(audioBuffer);\n }\n } catch (e) { /* Audio render failures are non-fatal */ }\n lastRenderedAudioEndMs = chunkEndMs;\n }\n \n // =====================================================================\n // EXACT same pattern as captureBatch: seekForRender + captureFromClone\n // =====================================================================\n const seekStart = performance.now();\n await renderClone.seekForRender(timeMs);\n totalSeekMs += performance.now() - seekStart;\n \n const captureStart = performance.now();\n const canvas = await captureFromClone(renderClone, renderContainer, {\n scale: config.scale,\n contentReadyMode: config.contentReadyMode,\n blockingTimeoutMs: config.blockingTimeoutMs,\n originalTimegroup: timegroup,\n });\n totalCaptureMs += performance.now() - captureStart;\n \n // Encode frame\n if (videoSource && output && encodingCtx) {\n const encodeStart = performance.now();\n encodingCtx.drawImage(\n canvas,\n 0, 0, canvas.width, canvas.height,\n 0, 0, config.videoWidth, config.videoHeight,\n );\n await videoSource.add(timestampS, config.frameDurationS);\n totalEncodeMs += performance.now() - encodeStart;\n }\n \n // Progress\n const currentFrame = frameIndex + 1;\n const progress = currentFrame / config.totalFrames;\n const renderedMs = currentFrame * config.frameDurationMs;\n const elapsedMs = performance.now() - renderStartTime;\n const msPerFrame = elapsedMs / currentFrame;\n const remainingFrames = config.totalFrames - currentFrame;\n const estimatedRemainingMs = remainingFrames * msPerFrame;\n const speedMultiplier = renderedMs / elapsedMs;\n \n if (onProgress && frameIndex % 10 === 0) {\n const previewWidth = 160;\n const previewHeight = Math.round(previewWidth * (config.videoHeight / config.videoWidth));\n const thumbCanvas = document.createElement(\"canvas\");\n thumbCanvas.width = previewWidth;\n thumbCanvas.height = previewHeight;\n const thumbCtx = thumbCanvas.getContext(\"2d\")!;\n thumbCtx.drawImage(canvas, 0, 0, previewWidth, previewHeight);\n lastFramePreviewUrl = thumbCanvas.toDataURL(\"image/jpeg\", 0.7);\n }\n \n onProgress?.({\n progress,\n currentFrame,\n totalFrames: config.totalFrames,\n renderedMs,\n totalDurationMs: config.renderDurationMs,\n elapsedMs,\n estimatedRemainingMs,\n speedMultiplier,\n framePreviewUrl: lastFramePreviewUrl,\n });\n }\n \n // Render remaining audio\n if (audioSource && lastRenderedAudioEndMs < config.endMs) {\n try {\n const audioBuffer = await timegroup.renderAudio(lastRenderedAudioEndMs, config.endMs);\n if (audioBuffer && audioBuffer.length > 0) {\n await audioSource.add(audioBuffer);\n }\n } catch (e) { /* Audio render failures are non-fatal */ }\n }\n \n const totalTime = performance.now() - renderStartTime;\n console.log(\n `[renderTimegroupToVideo] ${config.totalFrames} frames: ` +\n `seek=${totalSeekMs.toFixed(0)}ms, capture=${totalCaptureMs.toFixed(0)}ms, ` +\n `encode=${totalEncodeMs.toFixed(0)}ms, total=${totalTime.toFixed(0)}ms`\n );\n \n if (config.benchmarkMode) {\n return undefined;\n }\n \n await output!.finalize();\n \n if (useStreaming) {\n // Streaming mode: chunks already sent via customWritableStream or file stream\n return undefined;\n } else {\n const bufferTarget = target as BufferTarget;\n const videoBuffer = bufferTarget.buffer;\n if (!videoBuffer) {\n throw new Error(\"Video encoding failed: no buffer produced\");\n }\n \n if (config.returnBuffer) {\n return new Uint8Array(videoBuffer);\n }\n \n const videoBlob = new Blob([videoBuffer], { type: \"video/mp4\" });\n downloadBlob(videoBlob, config.filename);\n return undefined;\n }\n \n } finally {\n cleanupRenderClone();\n }\n}\n\nexport { QUALITY_HIGH };\nexport type { AudioCodec };\n"],"mappings":";;;;AAuEA,IAAa,6BAAb,cAAgD,MAAM;CACpD,YAAY,iBAA+B,iBAA+B;AACxE,QACE,+CAA+C,gBAAgB,KAAK,KAAK,CAAC,iBAC3D,gBAAgB,SAAS,IAAI,gBAAgB,KAAK,KAAK,GAAG,OAAO,GACjF;AACD,OAAK,OAAO;;;AAIhB,IAAa,uBAAb,cAA0C,MAAM;CAC9C,cAAc;AACZ,QAAM,mBAAmB;AACzB,OAAK,OAAO;;;AAiChB,SAAS,cACP,WACA,SACgB;CAChB,MAAM,MAAM,QAAQ,OAAO,UAAU,gBAAgB;CACrD,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,WAAW,QAAQ,YAAY;CACrC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,mBAAmB,QAAQ,oBAAoB;CACrD,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,mBAAmB,QAAQ,oBAAoB;CACrD,MAAM,oBAAoB,QAAQ,qBAAqB;CACvD,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,uBAAuB,QAAQ,wBAAwB,CAAC,OAAO,OAAO;CAC5E,MAAM,gBAAgB,QAAQ,iBAAiB;CAE/C,MAAM,kBAAkB,UAAU;AAClC,KAAI,CAAC,mBAAmB,mBAAmB,EACzC,OAAM,IAAI,MAAM,4BAA4B;CAG9C,MAAM,UAAU,KAAK,IAAI,GAAG,QAAQ,UAAU,EAAE;CAChD,MAAM,QAAQ,QAAQ,SAAS,SAAY,KAAK,IAAI,QAAQ,MAAM,gBAAgB,GAAG;CACrF,MAAM,mBAAmB,QAAQ;AAEjC,KAAI,oBAAoB,EACtB,OAAM,IAAI,MAAM,8BAA8B,QAAQ,QAAQ,MAAM,IAAI;CAG1E,MAAM,iBAAiB,UAAU,eAAe;CAChD,MAAM,kBAAkB,UAAU,gBAAgB;CAClD,MAAM,QAAQ,KAAK,MAAM,iBAAiB,MAAM;CAChD,MAAM,SAAS,KAAK,MAAM,kBAAkB,MAAM;CAElD,MAAM,aAAa,QAAQ,MAAM,IAAI,QAAQ,QAAQ;CACrD,MAAM,cAAc,SAAS,MAAM,IAAI,SAAS,SAAS;CAEzD,MAAM,kBAAkB,MAAO;AAI/B,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAfkB,KAAK,KAAK,mBAAmB,gBAAgB;EAgB/D;EACA,gBAhBqB,kBAAkB;EAiBvC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;AAOH,SAAS,8BAAuC;AAC9C,QAAO,OAAO,WAAW,eAAe,wBAAwB;;AAGlE,eAAe,sBACb,UACsF;AACtF,KAAI,CAAC,6BAA6B,CAChC,QAAO;AAGT,KAAI;EAKF,MAAM,WAAW,OAJE,MAAO,OAAe,mBAAmB;GAC1D,eAAe;GACf,OAAO,CAAC;IAAE,aAAa;IAAa,QAAQ,EAAE,aAAa,CAAC,OAAO,EAAE;IAAE,CAAC;GACzE,CAAC,EACgC,gBAAgB;AAClD,SAAO;GAAE;GAAU,OAAO,YAAY;AAAE,UAAM,SAAS,OAAO;;GAAK;UAC5D,GAAG;AACV,MAAK,EAAY,SAAS,aACxB,SAAQ,KAAK,8CAA8C,EAAE;AAE/D,SAAO;;;AAIX,eAAe,iBACb,iBACA,iBACqB;AACrB,MAAK,MAAM,SAAS,gBAClB,KAAI;AAEF,MADoB,MAAM,eAAe,OAAO,gBAAgB,CAC/C,QAAO;UACjB,GAAG;AACV,UAAQ,KAAK,uCAAuC,MAAM,IAAI,EAAE;;AAIpE,OAAM,IAAI,2BAA2B,iBADb,MAAM,wBAAwB,QAAW,gBAAgB,CACX;;AAGxE,SAAS,aAAa,MAAY,UAAwB;CACxD,MAAM,MAAM,IAAI,gBAAgB,KAAK;CACrC,MAAM,IAAI,SAAS,cAAc,IAAI;AACrC,GAAE,OAAO;AACT,GAAE,WAAW;AACb,UAAS,KAAK,YAAY,EAAE;AAC5B,GAAE,OAAO;AACT,UAAS,KAAK,YAAY,EAAE;AAC5B,KAAI,gBAAgB,IAAI;;;;;;;;AAsB1B,eAAsB,uBACpB,WACA,UAAgC,EAAE,EACD;CACjC,MAAM,SAAS,cAAc,WAAW,QAAQ;CAChD,MAAM,EAAE,QAAQ,eAAe;CAE/B,MAAM,uBAAuB;AAC3B,MAAI,QAAQ,QAAS,OAAM,IAAI,sBAAsB;;AAGvD,mBAAkB;CAKlB,MAAM,EAAE,OAAO,aAAa,WAAW,iBAAiB,SAAS,uBAC/D,MAAM,UAAU,mBAAmB;CAKrC,MAAMA,aAAuB,EAAE;AAC/B,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,aAAa,IACtC,YAAW,KAAK,OAAO,UAAU,IAAI,OAAO,gBAAgB;CAG9D,MAAM,gBAAgB,YAAY,iBAAiB,WAAW;AAC9D,KAAI,cAAc,SAAS,GAAG;AAC5B,UAAQ,IAAI,gEAAgE,cAAc,OAAO,cAAc;AAC/G,QAAM,QAAQ,IACZ,MAAM,KAAK,cAAc,CAAC,KAAK,UAC5B,MAAkB,0BAA0B,WAAW,CACzD,CACF;AACD,UAAQ,IAAI,6CAA6C;;CAM3D,IAAIC,SAAwB;CAC5B,IAAIC,cAAmC;CACvC,IAAIC,cAAwC;CAC5C,IAAIC,SAA6C;CACjD,IAAIC,aAA0F;CAC9F,IAAI,eAAe;CACnB,IAAIC,iBAAyC;CAC7C,IAAIC,cAAwD;AAE5D,KAAI,CAAC,OAAO,eAAe;AAEzB,MAAI,QAAQ,sBAAsB;AAChC,YAAS,IAAI,aAAa,QAAQ,qBAA4B;AAC9D,YAAS,IAAI,OAAO;IAClB,QAAQ,IAAI,gBAAgB,EAAE,WAAW,cAAc,CAAC;IACxD;IACD,CAAC;AACF,kBAAe;aACN,OAAO,WAAW;AAC3B,gBAAa,MAAM,sBAAsB,OAAO,SAAS;AACzD,kBAAe,eAAe;AAE9B,OAAI,gBAAgB,YAAY;AAC9B,aAAS,IAAI,aAAa,WAAW,SAAgB;AACrD,aAAS,IAAI,OAAO;KAClB,QAAQ,IAAI,gBAAgB,EAAE,WAAW,cAAc,CAAC;KACxD;KACD,CAAC;;;AAIN,MAAI,CAAC,QAAQ;AACX,YAAS,IAAI,cAAc;AAC3B,YAAS,IAAI,OAAO;IAAE,QAAQ,IAAI,iBAAiB;IAAE;IAAQ,CAAC;;AAGhE,mBAAiB,IAAI,gBAAgB,OAAO,YAAY,OAAO,YAAY;AAC3E,gBAAc,eAAe,WAAW,KAAK;AAC7C,MAAI,CAAC,aAAa;AAChB,uBAAoB;AACpB,SAAM,IAAI,MAAM,wCAAwC;;EAG1D,MAAMC,cAAmC;GACvC,OAAO,OAAO;GACd,SAAS,OAAO;GAChB,kBAAkB,OAAO;GAC1B;AACD,gBAAc,IAAI,aAAa,gBAAgB,YAAY;AAC3D,SAAO,cAAc,YAAY;AAEjC,MAAI,OAAO,cAAc;AAUvB,iBAAc,IAAI,kBAJuB;IACvC,OANoB,MAAM,iBAAiB,OAAO,sBAAsB;KACxE,kBAAkB;KAClB,YAAY;KACZ,SAAS,OAAO;KACjB,CAAC;IAGA,SAAS,OAAO;IACjB,CAC+C;AAChD,UAAO,cAAc,YAAY;;AAGnC,QAAM,OAAO,OAAO;;CAMtB,MAAM,kBAAkB,YAAY,KAAK;CACzC,IAAIC;CACJ,IAAI,yBAAyB,OAAO;CACpC,MAAM,uBAAuB;CAE7B,IAAI,cAAc;CAClB,IAAI,iBAAiB;CACrB,IAAI,gBAAgB;AAEpB,KAAI;AACF,OAAK,IAAI,aAAa,GAAG,aAAa,OAAO,aAAa,cAAc;AACtE,mBAAgB;GAEhB,MAAM,SAAS,WAAW;GAC1B,MAAM,aAAc,aAAa,OAAO,kBAAmB;AAG3D,OAAI,eAAe,UAAU,yBAAyB,sBAAsB;IAC1E,MAAM,aAAa,KAAK,IAAI,SAAS,sBAAsB,OAAO,MAAM;AACxE,QAAI;KACF,MAAM,cAAc,MAAM,UAAU,YAAY,wBAAwB,WAAW;AACnF,SAAI,eAAe,YAAY,SAAS,EACtC,OAAM,YAAY,IAAI,YAAY;aAE7B,GAAG;AACZ,6BAAyB;;GAM3B,MAAM,YAAY,YAAY,KAAK;AACnC,SAAM,YAAY,cAAc,OAAO;AACvC,kBAAe,YAAY,KAAK,GAAG;GAEnC,MAAM,eAAe,YAAY,KAAK;GACtC,MAAM,SAAS,MAAM,iBAAiB,aAAa,iBAAiB;IAClE,OAAO,OAAO;IACd,kBAAkB,OAAO;IACzB,mBAAmB,OAAO;IAC1B,mBAAmB;IACpB,CAAC;AACF,qBAAkB,YAAY,KAAK,GAAG;AAGtC,OAAI,eAAe,UAAU,aAAa;IACxC,MAAM,cAAc,YAAY,KAAK;AACrC,gBAAY,UACV,QACA,GAAG,GAAG,OAAO,OAAO,OAAO,QAC3B,GAAG,GAAG,OAAO,YAAY,OAAO,YACjC;AACD,UAAM,YAAY,IAAI,YAAY,OAAO,eAAe;AACxD,qBAAiB,YAAY,KAAK,GAAG;;GAIvC,MAAM,eAAe,aAAa;GAClC,MAAM,WAAW,eAAe,OAAO;GACvC,MAAM,aAAa,eAAe,OAAO;GACzC,MAAM,YAAY,YAAY,KAAK,GAAG;GACtC,MAAM,aAAa,YAAY;GAE/B,MAAM,wBADkB,OAAO,cAAc,gBACE;GAC/C,MAAM,kBAAkB,aAAa;AAErC,OAAI,cAAc,aAAa,OAAO,GAAG;IACvC,MAAM,eAAe;IACrB,MAAM,gBAAgB,KAAK,MAAM,gBAAgB,OAAO,cAAc,OAAO,YAAY;IACzF,MAAM,cAAc,SAAS,cAAc,SAAS;AACpD,gBAAY,QAAQ;AACpB,gBAAY,SAAS;AAErB,IADiB,YAAY,WAAW,KAAK,CACpC,UAAU,QAAQ,GAAG,GAAG,cAAc,cAAc;AAC7D,0BAAsB,YAAY,UAAU,cAAc,GAAI;;AAGhE,gBAAa;IACX;IACA;IACA,aAAa,OAAO;IACpB;IACA,iBAAiB,OAAO;IACxB;IACA;IACA;IACA,iBAAiB;IAClB,CAAC;;AAIJ,MAAI,eAAe,yBAAyB,OAAO,MACjD,KAAI;GACF,MAAM,cAAc,MAAM,UAAU,YAAY,wBAAwB,OAAO,MAAM;AACrF,OAAI,eAAe,YAAY,SAAS,EACtC,OAAM,YAAY,IAAI,YAAY;WAE7B,GAAG;EAGd,MAAM,YAAY,YAAY,KAAK,GAAG;AACtC,UAAQ,IACN,4BAA4B,OAAO,YAAY,gBACvC,YAAY,QAAQ,EAAE,CAAC,cAAc,eAAe,QAAQ,EAAE,CAAC,aAC7D,cAAc,QAAQ,EAAE,CAAC,YAAY,UAAU,QAAQ,EAAE,CAAC,IACrE;AAED,MAAI,OAAO,cACT;AAGF,QAAM,OAAQ,UAAU;AAExB,MAAI,aAEF;OACK;GAEL,MAAM,cADe,OACY;AACjC,OAAI,CAAC,YACH,OAAM,IAAI,MAAM,4CAA4C;AAG9D,OAAI,OAAO,aACT,QAAO,IAAI,WAAW,YAAY;AAIpC,gBADkB,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,aAAa,CAAC,EACxC,OAAO,SAAS;AACxC;;WAGM;AACR,sBAAoB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { renderTimegroupToVideo } from "../preview/renderTimegroupToVideo.js";
|
|
2
|
+
import { getRenderInfo } from "../getRenderInfo.js";
|
|
3
|
+
|
|
4
|
+
//#region src/render/EFRenderAPI.ts
|
|
5
|
+
function findRootTimegroup() {
|
|
6
|
+
const workbench = document.querySelector("ef-workbench");
|
|
7
|
+
if (workbench) {
|
|
8
|
+
const timegroup = workbench.querySelector("ef-timegroup");
|
|
9
|
+
if (timegroup) return timegroup;
|
|
10
|
+
}
|
|
11
|
+
return document.querySelector("ef-timegroup");
|
|
12
|
+
}
|
|
13
|
+
function setWorkbenchRendering(rendering) {
|
|
14
|
+
const workbench = document.querySelector("ef-workbench");
|
|
15
|
+
if (workbench) workbench.rendering = rendering;
|
|
16
|
+
}
|
|
17
|
+
const api = {
|
|
18
|
+
async renderStreaming(options = {}) {
|
|
19
|
+
const timegroup = findRootTimegroup();
|
|
20
|
+
if (!timegroup) throw new Error("No ef-timegroup found. Cannot render.");
|
|
21
|
+
if (typeof window === "undefined" || !window.onRenderChunk) throw new Error("window.onRenderChunk is not set. Call page.exposeFunction('onRenderChunk', callback) from Playwright first.");
|
|
22
|
+
setWorkbenchRendering(true);
|
|
23
|
+
try {
|
|
24
|
+
await timegroup.waitForMediaDurations();
|
|
25
|
+
const chunkWriter = new WritableStream({ write(chunk) {
|
|
26
|
+
if (window.onRenderChunk) window.onRenderChunk(chunk);
|
|
27
|
+
} });
|
|
28
|
+
const onProgress = options.onProgress || window.onRenderProgress;
|
|
29
|
+
await renderTimegroupToVideo(timegroup, {
|
|
30
|
+
...options,
|
|
31
|
+
customWritableStream: chunkWriter,
|
|
32
|
+
onProgress,
|
|
33
|
+
returnBuffer: false
|
|
34
|
+
});
|
|
35
|
+
} finally {
|
|
36
|
+
setWorkbenchRendering(false);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async render(options = {}) {
|
|
40
|
+
const timegroup = findRootTimegroup();
|
|
41
|
+
if (!timegroup) throw new Error("No ef-timegroup found. Cannot render.");
|
|
42
|
+
setWorkbenchRendering(true);
|
|
43
|
+
try {
|
|
44
|
+
await timegroup.waitForMediaDurations();
|
|
45
|
+
const onProgress = options.onProgress || window.onRenderProgress;
|
|
46
|
+
const buffer = await renderTimegroupToVideo(timegroup, {
|
|
47
|
+
...options,
|
|
48
|
+
returnBuffer: true,
|
|
49
|
+
onProgress
|
|
50
|
+
});
|
|
51
|
+
if (!buffer) throw new Error("Render failed: no buffer returned");
|
|
52
|
+
return buffer;
|
|
53
|
+
} finally {
|
|
54
|
+
setWorkbenchRendering(false);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
async getRenderInfo() {
|
|
58
|
+
return getRenderInfo();
|
|
59
|
+
},
|
|
60
|
+
isReady() {
|
|
61
|
+
return findRootTimegroup() !== null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
if (typeof window !== "undefined") window.EF_RENDER = api;
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//# sourceMappingURL=EFRenderAPI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EFRenderAPI.js","names":["api: IEFRenderAPI"],"sources":["../../src/render/EFRenderAPI.ts"],"sourcesContent":["/**\n * Window API for programmatic video rendering.\n * \n * Exposes renderTimegroupToVideo for use from Playwright/CLI.\n * Supports streaming output and custom data injection.\n */\n\nimport type { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport type { EFWorkbench } from \"../gui/EFWorkbench.js\";\nimport { getRenderInfo, type RenderInfo } from \"../getRenderInfo.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n type RenderProgress,\n} from \"../preview/renderTimegroupToVideo.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IEFRenderAPI {\n /**\n * Render with streaming output (calls window.onRenderChunk for each chunk).\n * Use this for CLI/Playwright to avoid memory buffering.\n */\n renderStreaming(options?: RenderToVideoOptions): Promise<void>;\n\n /**\n * Render and return buffer (for shorter videos or in-browser use).\n * Returns the video as Uint8Array.\n */\n render(options?: RenderToVideoOptions): Promise<Uint8Array>;\n\n /**\n * Get render info (dimensions, duration, assets).\n * Same as the exported getRenderInfo function.\n */\n getRenderInfo(): Promise<RenderInfo>;\n\n /**\n * Check if SDK is ready for rendering.\n * Returns true if a root timegroup is found.\n */\n isReady(): boolean;\n}\n\ndeclare global {\n interface Window {\n EF_RENDER?: IEFRenderAPI;\n EF_RENDER_DATA?: Record<string, unknown>;\n onRenderChunk?: (chunk: Uint8Array) => void; // Set by Playwright\n onRenderProgress?: (progress: RenderProgress) => void; // Optional progress callback\n }\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nfunction findRootTimegroup(): EFTimegroup | null {\n // Try to find timegroup from workbench first\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n const timegroup = workbench.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n if (timegroup) {\n return timegroup;\n }\n }\n\n // Fallback: find first root timegroup\n const rootTimegroup = document.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n return rootTimegroup;\n}\n\nfunction setWorkbenchRendering(rendering: boolean): void {\n const workbench = document.querySelector(\"ef-workbench\") as EFWorkbench | null;\n if (workbench) {\n workbench.rendering = rendering;\n }\n}\n\nconst api: IEFRenderAPI = {\n async renderStreaming(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Check if window.onRenderChunk is available\n if (typeof window === \"undefined\" || !window.onRenderChunk) {\n throw new Error(\n \"window.onRenderChunk is not set. \" +\n \"Call page.exposeFunction('onRenderChunk', callback) from Playwright first.\"\n );\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Create custom writable stream that calls window.onRenderChunk\n const chunkWriter = new WritableStream<Uint8Array>({\n write(chunk: Uint8Array) {\n if (window.onRenderChunk) {\n window.onRenderChunk(chunk);\n }\n },\n });\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n // Render with custom stream\n await renderTimegroupToVideo(timegroup, {\n ...options,\n customWritableStream: chunkWriter,\n onProgress,\n returnBuffer: false,\n });\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async render(options: RenderToVideoOptions = {}): Promise<Uint8Array> {\n const timegroup = findRootTimegroup();\n if (!timegroup) {\n throw new Error(\"No ef-timegroup found. Cannot render.\");\n }\n\n // Hide workbench UI during render\n setWorkbenchRendering(true);\n\n try {\n // Wait for media to be ready\n await timegroup.waitForMediaDurations();\n\n // Merge progress callback if window.onRenderProgress is set\n const onProgress = options.onProgress || window.onRenderProgress;\n\n const buffer = await renderTimegroupToVideo(timegroup, {\n ...options,\n returnBuffer: true,\n onProgress,\n });\n\n if (!buffer) {\n throw new Error(\"Render failed: no buffer returned\");\n }\n\n return buffer;\n } finally {\n // Restore workbench UI\n setWorkbenchRendering(false);\n }\n },\n\n async getRenderInfo(): Promise<RenderInfo> {\n return getRenderInfo();\n },\n\n isReady(): boolean {\n return findRootTimegroup() !== null;\n },\n};\n\n// Export and register on window\nif (typeof window !== \"undefined\") {\n window.EF_RENDER = api;\n}\n\nexport { api as EFRenderAPI };\nexport type { IEFRenderAPI as EFRenderAPIInterface };\n"],"mappings":";;;;AA2DA,SAAS,oBAAwC;CAE/C,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,KAAI,WAAW;EACb,MAAM,YAAY,UAAU,cAAc,eAAe;AACzD,MAAI,UACF,QAAO;;AAMX,QADsB,SAAS,cAAc,eAAe;;AAI9D,SAAS,sBAAsB,WAA0B;CACvD,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,KAAI,UACF,WAAU,YAAY;;AAI1B,MAAMA,MAAoB;CACxB,MAAM,gBAAgB,UAAgC,EAAE,EAAiB;EACvE,MAAM,YAAY,mBAAmB;AACrC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wCAAwC;AAI1D,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,cAC3C,OAAM,IAAI,MACR,8GAED;AAIH,wBAAsB,KAAK;AAE3B,MAAI;AAEF,SAAM,UAAU,uBAAuB;GAGvC,MAAM,cAAc,IAAI,eAA2B,EACjD,MAAM,OAAmB;AACvB,QAAI,OAAO,cACT,QAAO,cAAc,MAAM;MAGhC,CAAC;GAGF,MAAM,aAAa,QAAQ,cAAc,OAAO;AAGhD,SAAM,uBAAuB,WAAW;IACtC,GAAG;IACH,sBAAsB;IACtB;IACA,cAAc;IACf,CAAC;YACM;AAER,yBAAsB,MAAM;;;CAIhC,MAAM,OAAO,UAAgC,EAAE,EAAuB;EACpE,MAAM,YAAY,mBAAmB;AACrC,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wCAAwC;AAI1D,wBAAsB,KAAK;AAE3B,MAAI;AAEF,SAAM,UAAU,uBAAuB;GAGvC,MAAM,aAAa,QAAQ,cAAc,OAAO;GAEhD,MAAM,SAAS,MAAM,uBAAuB,WAAW;IACrD,GAAG;IACH,cAAc;IACd;IACD,CAAC;AAEF,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,oCAAoC;AAGtD,UAAO;YACC;AAER,yBAAsB,MAAM;;;CAIhC,MAAM,gBAAqC;AACzC,SAAO,eAAe;;CAGxB,UAAmB;AACjB,SAAO,mBAAmB,KAAK;;CAElC;AAGD,IAAI,OAAO,WAAW,YACpB,QAAO,YAAY"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/render/getRenderData.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Helper function for compositions to read custom render data.
|
|
4
|
+
*
|
|
5
|
+
* Supports both runtime data (set by Playwright/CLI via window.EF_RENDER_DATA)
|
|
6
|
+
* and build-time data (set by Vite define as RENDER_DATA).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Get custom render data that was passed to the render process.
|
|
10
|
+
*
|
|
11
|
+
* @returns The render data object, or undefined if no data was provided
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { getRenderData } from "@editframe/elements";
|
|
16
|
+
*
|
|
17
|
+
* interface MyRenderData {
|
|
18
|
+
* userName: string;
|
|
19
|
+
* theme: "light" | "dark";
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* const data = getRenderData<MyRenderData>();
|
|
23
|
+
* if (data) {
|
|
24
|
+
* console.log(data.userName); // "John"
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function getRenderData<T = unknown>(): T | undefined;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { getRenderData };
|
|
31
|
+
//# sourceMappingURL=getRenderData.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/render/getRenderData.ts
|
|
2
|
+
/**
|
|
3
|
+
* Get custom render data that was passed to the render process.
|
|
4
|
+
*
|
|
5
|
+
* @returns The render data object, or undefined if no data was provided
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { getRenderData } from "@editframe/elements";
|
|
10
|
+
*
|
|
11
|
+
* interface MyRenderData {
|
|
12
|
+
* userName: string;
|
|
13
|
+
* theme: "light" | "dark";
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* const data = getRenderData<MyRenderData>();
|
|
17
|
+
* if (data) {
|
|
18
|
+
* console.log(data.userName); // "John"
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function getRenderData() {
|
|
23
|
+
if (typeof window !== "undefined" && window.EF_RENDER_DATA) return window.EF_RENDER_DATA;
|
|
24
|
+
if (typeof RENDER_DATA !== "undefined") return RENDER_DATA;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { getRenderData };
|
|
29
|
+
//# sourceMappingURL=getRenderData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getRenderData.js","names":[],"sources":["../../src/render/getRenderData.ts"],"sourcesContent":["/**\n * Helper function for compositions to read custom render data.\n * \n * Supports both runtime data (set by Playwright/CLI via window.EF_RENDER_DATA)\n * and build-time data (set by Vite define as RENDER_DATA).\n */\n\n// Declare RENDER_DATA for TypeScript (set via Vite define at build time)\ndeclare const RENDER_DATA: unknown | undefined;\n\n/**\n * Get custom render data that was passed to the render process.\n * \n * @returns The render data object, or undefined if no data was provided\n * \n * @example\n * ```typescript\n * import { getRenderData } from \"@editframe/elements\";\n * \n * interface MyRenderData {\n * userName: string;\n * theme: \"light\" | \"dark\";\n * }\n * \n * const data = getRenderData<MyRenderData>();\n * if (data) {\n * console.log(data.userName); // \"John\"\n * }\n * ```\n */\nexport function getRenderData<T = unknown>(): T | undefined {\n // Runtime data (set by Playwright/CLI)\n if (typeof window !== \"undefined\" && window.EF_RENDER_DATA) {\n return window.EF_RENDER_DATA as T;\n }\n \n // Build-time data (set by Vite define)\n if (typeof RENDER_DATA !== \"undefined\") {\n return RENDER_DATA as T;\n }\n \n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,gBAA4C;AAE1D,KAAI,OAAO,WAAW,eAAe,OAAO,eAC1C,QAAO,OAAO;AAIhB,KAAI,OAAO,gBAAgB,YACzB,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "UNLICENSED",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@bramus/style-observer": "^1.3.0",
|
|
16
|
-
"@editframe/assets": "0.
|
|
16
|
+
"@editframe/assets": "0.32.0-beta.0",
|
|
17
17
|
"@lit/context": "^1.1.6",
|
|
18
18
|
"@lit/task": "^1.0.3",
|
|
19
19
|
"@opentelemetry/api": "^1.9.0",
|