@editframe/elements 0.37.3-beta → 0.38.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/EF_FRAMEGEN.js +17 -14
- package/dist/EF_FRAMEGEN.js.map +1 -1
- package/dist/EF_RENDERING.js.map +1 -1
- package/dist/canvas/EFCanvas.d.ts +9 -2
- package/dist/canvas/EFCanvas.js +14 -4
- package/dist/canvas/EFCanvas.js.map +1 -1
- package/dist/canvas/EFCanvasItem.d.ts +4 -4
- package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
- package/dist/canvas/overlays/SelectionOverlay.js +5 -12
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- package/dist/canvas/overlays/overlayState.js.map +1 -1
- package/dist/canvas/selection/SelectionController.js.map +1 -1
- package/dist/elements/EFAudio.d.ts +1 -11
- package/dist/elements/EFAudio.js +2 -10
- package/dist/elements/EFAudio.js.map +1 -1
- package/dist/elements/EFCaptions.d.ts +5 -9
- package/dist/elements/EFCaptions.js +34 -11
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.d.ts +10 -8
- package/dist/elements/EFImage.js +117 -32
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
- package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
- package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
- package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
- package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
- package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +14 -8
- package/dist/elements/EFMedia.js +52 -19
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFPanZoom.d.ts +2 -2
- package/dist/elements/EFPanZoom.js +1 -1
- package/dist/elements/EFPanZoom.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +16 -8
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +7 -10
- package/dist/elements/EFSurface.js +4 -43
- package/dist/elements/EFSurface.js.map +1 -1
- package/dist/elements/EFTemporal.d.ts +33 -8
- package/dist/elements/EFTemporal.js +92 -40
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFText.d.ts +3 -0
- package/dist/elements/EFText.js +54 -21
- package/dist/elements/EFText.js.map +1 -1
- package/dist/elements/EFTextSegment.js +8 -4
- package/dist/elements/EFTextSegment.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +26 -43
- package/dist/elements/EFTimegroup.js +295 -314
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.d.ts +44 -42
- package/dist/elements/EFVideo.js +259 -172
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/elements/EFWaveform.d.ts +3 -8
- package/dist/elements/EFWaveform.js +18 -13
- package/dist/elements/EFWaveform.js.map +1 -1
- package/dist/elements/ElementPositionInfo.js.map +1 -1
- package/dist/elements/FetchMixin.js.map +1 -1
- package/dist/elements/TargetController.d.ts +0 -3
- package/dist/elements/TargetController.js +12 -35
- package/dist/elements/TargetController.js.map +1 -1
- package/dist/elements/TimegroupController.js.map +1 -1
- package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
- package/dist/elements/cloneFactoryRegistry.js +15 -0
- package/dist/elements/cloneFactoryRegistry.js.map +1 -0
- package/dist/elements/renderTemporalAudio.js +8 -6
- package/dist/elements/renderTemporalAudio.js.map +1 -1
- package/dist/elements/setupTemporalHierarchy.js +62 -0
- package/dist/elements/setupTemporalHierarchy.js.map +1 -0
- package/dist/elements/updateAnimations.js +62 -87
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/getRenderInfo.d.ts +3 -2
- package/dist/getRenderInfo.js +20 -4
- package/dist/getRenderInfo.js.map +1 -1
- package/dist/gui/ContextMixin.js +68 -12
- package/dist/gui/ContextMixin.js.map +1 -1
- package/dist/gui/Controllable.js +1 -1
- package/dist/gui/Controllable.js.map +1 -1
- package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
- package/dist/gui/EFActiveRootTemporal.js.map +1 -1
- package/dist/gui/EFControls.d.ts +2 -2
- package/dist/gui/EFControls.js +2 -2
- package/dist/gui/EFControls.js.map +1 -1
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFDial.js +12 -9
- package/dist/gui/EFDial.js.map +1 -1
- package/dist/gui/EFFilmstrip.d.ts +2 -0
- package/dist/gui/EFFilmstrip.js +18 -10
- package/dist/gui/EFFilmstrip.js.map +1 -1
- package/dist/gui/EFFitScale.d.ts +28 -4
- package/dist/gui/EFFitScale.js +88 -26
- package/dist/gui/EFFitScale.js.map +1 -1
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.js +3 -3
- package/dist/gui/EFFocusOverlay.js.map +1 -1
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPause.js +1 -1
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFPlay.js +1 -1
- package/dist/gui/EFPreview.js +1 -1
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFResizableBox.js +5 -5
- package/dist/gui/EFResizableBox.js.map +1 -1
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFScrubber.js +8 -13
- package/dist/gui/EFScrubber.js.map +1 -1
- package/dist/gui/EFTimeDisplay.d.ts +8 -4
- package/dist/gui/EFTimeDisplay.js +25 -7
- package/dist/gui/EFTimeDisplay.js.map +1 -1
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.js +3 -3
- package/dist/gui/EFTimelineRuler.js.map +1 -1
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFToggleLoop.js +1 -1
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTogglePlay.js +1 -1
- package/dist/gui/EFTransformHandles.d.ts +4 -4
- package/dist/gui/EFTransformHandles.js +6 -6
- package/dist/gui/EFTransformHandles.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +40 -36
- package/dist/gui/EFWorkbench.js +436 -822
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/FitScaleHelpers.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +3 -8
- package/dist/gui/PlaybackController.js +59 -56
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/gui/TargetOrContextMixin.js +43 -6
- package/dist/gui/TargetOrContextMixin.js.map +1 -1
- package/dist/gui/ef-theme.css +136 -0
- package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
- package/dist/gui/hierarchy/EFHierarchy.js +14 -24
- package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
- package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
- package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
- package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
- package/dist/gui/icons.js.map +1 -1
- package/dist/gui/previewSettingsContext.d.ts +18 -0
- package/dist/gui/previewSettingsContext.js.map +1 -1
- package/dist/gui/theme.js +34 -0
- package/dist/gui/theme.js.map +1 -0
- package/dist/gui/timeline/EFTimeline.d.ts +2 -2
- package/dist/gui/timeline/EFTimeline.js +70 -52
- package/dist/gui/timeline/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +3 -1
- package/dist/gui/timeline/EFTimelineRow.js +55 -32
- package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
- package/dist/gui/timeline/TrimHandles.d.ts +23 -9
- package/dist/gui/timeline/TrimHandles.js +224 -51
- package/dist/gui/timeline/TrimHandles.js.map +1 -1
- package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
- package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
- package/dist/gui/timeline/timelineEditingContext.js +24 -0
- package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
- package/dist/gui/timeline/timelineStateContext.js.map +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
- package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
- package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
- package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
- package/dist/gui/timeline/tracks/TextTrack.js +17 -43
- package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
- package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
- package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
- package/dist/gui/timeline/tracks/TrackItem.js +18 -17
- package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
- package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTree.js +8 -14
- package/dist/gui/tree/EFTree.js.map +1 -1
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.js +3 -3
- package/dist/gui/tree/EFTreeItem.js.map +1 -1
- package/dist/gui/tree/treeContext.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/preview/AdaptiveResolutionTracker.js +3 -3
- package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
- package/dist/preview/FrameController.d.ts +2 -17
- package/dist/preview/FrameController.js +40 -63
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
- package/dist/preview/QualityUpgradeScheduler.js +158 -0
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
- package/dist/preview/RenderContext.d.ts +119 -1
- package/dist/preview/RenderContext.js +21 -3
- package/dist/preview/RenderContext.js.map +1 -1
- package/dist/preview/RenderProfiler.js.map +1 -1
- package/dist/preview/RenderStats.js +85 -0
- package/dist/preview/RenderStats.js.map +1 -0
- package/dist/preview/encoding/canvasEncoder.js +2 -52
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/encoding/workerEncoder.js.map +1 -1
- package/dist/preview/logger.js.map +1 -1
- package/dist/preview/previewSettings.d.ts +34 -0
- package/dist/preview/previewSettings.js +29 -17
- package/dist/preview/previewSettings.js.map +1 -1
- package/dist/preview/previewTypes.js +4 -4
- package/dist/preview/previewTypes.js.map +1 -1
- package/dist/preview/renderElementToCanvas.d.ts +44 -0
- package/dist/preview/renderElementToCanvas.js +72 -0
- package/dist/preview/renderElementToCanvas.js.map +1 -0
- package/dist/preview/renderTimegroupToCanvas.js +267 -145
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +30 -0
- package/dist/preview/renderTimegroupToVideo.js +85 -105
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/{renderTimegroupToVideo.d.ts → renderTimegroupToVideo.types.d.ts} +9 -9
- package/dist/preview/renderVideoToVideo.js +286 -0
- package/dist/preview/renderVideoToVideo.js.map +1 -0
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/ScaleConfig.js +74 -0
- package/dist/preview/rendering/ScaleConfig.js.map +1 -0
- package/dist/preview/rendering/inlineImages.js +1 -44
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.js +22 -0
- package/dist/preview/rendering/loadImage.js.map +1 -0
- package/dist/preview/rendering/renderToImageNative.js +3 -3
- package/dist/preview/rendering/renderToImageNative.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
- package/dist/preview/statsTrackingStrategy.js +1 -101
- package/dist/preview/statsTrackingStrategy.js.map +1 -1
- package/dist/preview/workers/WorkerPool.js +0 -1
- package/dist/preview/workers/WorkerPool.js.map +1 -1
- package/dist/preview/workers/encoderWorkerInline.js +21 -54
- package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
- package/dist/render/EFRenderAPI.d.ts +2 -1
- package/dist/render/EFRenderAPI.js +12 -36
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/render/getRenderData.js +4 -4
- package/dist/render/getRenderData.js.map +1 -1
- package/dist/style.css +114 -163
- package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
- package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +1 -1
- package/dist/transcoding/utils/UrlGenerator.js +10 -3
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/dist/utils/LRUCache.js +1 -0
- package/dist/utils/LRUCache.js.map +1 -1
- package/dist/utils/frameTime.js +23 -1
- package/dist/utils/frameTime.js.map +1 -1
- package/package.json +21 -8
- package/scripts/build-css.js +8 -1
- package/test/setup.ts +0 -1
- package/test/useAssetMSW.ts +50 -0
- package/test/visualRegressionUtils.ts +23 -9
- package/dist/_virtual/rolldown_runtime.js +0 -27
- package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
- package/dist/elements/EFThumbnailStrip.d.ts +0 -167
- package/dist/elements/EFThumbnailStrip.js +0 -731
- package/dist/elements/EFThumbnailStrip.js.map +0 -1
- package/dist/elements/SessionThumbnailCache.js +0 -154
- package/dist/elements/SessionThumbnailCache.js.map +0 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
- package/dist/node_modules/react/cjs/react.development.js +0 -1521
- package/dist/node_modules/react/cjs/react.development.js.map +0 -1
- package/dist/node_modules/react/index.js +0 -13
- package/dist/node_modules/react/index.js.map +0 -1
- package/dist/node_modules/react/jsx-runtime.js +0 -13
- package/dist/node_modules/react/jsx-runtime.js.map +0 -1
- package/dist/preview/encoding/types.d.ts +0 -1
- package/dist/preview/renderTimegroupPreview.js +0 -686
- package/dist/preview/renderTimegroupPreview.js.map +0 -1
- package/dist/preview/renderTimegroupToCanvas.d.ts +0 -42
- package/dist/preview/rendering/renderToImage.d.ts +0 -2
- package/dist/preview/rendering/renderToImage.js +0 -95
- package/dist/preview/rendering/renderToImage.js.map +0 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
- package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
- package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
- package/dist/preview/rendering/svgSerializer.js +0 -43
- package/dist/preview/rendering/svgSerializer.js.map +0 -1
- package/dist/preview/rendering/types.d.ts +0 -2
- package/dist/preview/thumbnailCacheSettings.js +0 -52
- package/dist/preview/thumbnailCacheSettings.js.map +0 -1
- package/dist/sandbox/PlaybackControls.d.ts +0 -1
- package/dist/sandbox/PlaybackControls.js +0 -10
- package/dist/sandbox/PlaybackControls.js.map +0 -1
- package/dist/sandbox/ScenarioRunner.d.ts +0 -1
- package/dist/sandbox/ScenarioRunner.js +0 -1
- package/dist/sandbox/defineSandbox.d.ts +0 -1
- package/dist/sandbox/index.d.ts +0 -3
- package/dist/sandbox/index.js +0 -2
- package/test/EFVideo.framegen.browsertest.ts +0 -80
- package/test/thumbnail-performance-test.html +0 -116
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { TrackItem } from "./TrackItem.js";
|
|
2
2
|
import { Caption } from "../../../elements/EFCaptions.js";
|
|
3
|
-
import * as
|
|
3
|
+
import * as lit43 from "lit";
|
|
4
4
|
import { TemplateResult, nothing } from "lit";
|
|
5
5
|
|
|
6
6
|
//#region src/gui/timeline/tracks/CaptionsTrack.d.ts
|
|
7
7
|
declare class EFCaptionsTrack extends TrackItem {
|
|
8
|
-
static styles:
|
|
8
|
+
static styles: lit43.CSSResult[];
|
|
9
9
|
contextCurrentTimeMs: number;
|
|
10
|
-
private _timeController;
|
|
11
10
|
private lastPixelsPerMs;
|
|
12
11
|
protected updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
13
12
|
render(): TemplateResult<1>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { currentTimeContext } from "../../currentTimeContext.js";
|
|
2
2
|
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
|
|
3
3
|
import { ICONS, phosphorIcon } from "../../icons.js";
|
|
4
|
+
import { getElementTypeColor } from "../../theme.js";
|
|
4
5
|
import { TrackItem } from "./TrackItem.js";
|
|
5
6
|
import { consume } from "@lit/context";
|
|
6
7
|
import { css, html, nothing } from "lit";
|
|
@@ -37,7 +38,7 @@ function measureTextWidth(text, fontSize, fontWeight = 500) {
|
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
40
|
* Check if words can fit individually within a segment when positioned by time
|
|
40
|
-
*
|
|
41
|
+
*
|
|
41
42
|
* Strategy: Allow overlaps as long as all words can be rendered within the container.
|
|
42
43
|
* Only use compact mode when words are so cramped they can't be displayed at all.
|
|
43
44
|
*/
|
|
@@ -73,69 +74,10 @@ function canWordsFitIndividually(words, segmentStart, segmentWidthPx, pixelsPerM
|
|
|
73
74
|
reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)`
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Controller to ensure captions track updates reactively during playback.
|
|
78
|
-
*
|
|
79
|
-
* Performance optimization: Only requests updates when the visual state actually
|
|
80
|
-
* needs to change (active word/segment changed), not on every frame.
|
|
81
|
-
*/
|
|
82
|
-
var CaptionsTimeController = class CaptionsTimeController {
|
|
83
|
-
static {
|
|
84
|
-
this.MIN_TIME_CHANGE_MS = 100;
|
|
85
|
-
}
|
|
86
|
-
constructor(host) {
|
|
87
|
-
this.host = host;
|
|
88
|
-
this.lastTimeMs = -1;
|
|
89
|
-
this.lastActiveWordIndex = -1;
|
|
90
|
-
this.lastActiveSegmentIndex = -1;
|
|
91
|
-
this.host.addController(this);
|
|
92
|
-
}
|
|
93
|
-
hostConnected() {
|
|
94
|
-
this.startTimeUpdate();
|
|
95
|
-
}
|
|
96
|
-
hostDisconnected() {
|
|
97
|
-
this.stopTimeUpdate();
|
|
98
|
-
}
|
|
99
|
-
startTimeUpdate() {
|
|
100
|
-
const update = () => {
|
|
101
|
-
const captions = this.host.element;
|
|
102
|
-
const currentTimeMs = captions.rootTimegroup?.currentTimeMs || 0;
|
|
103
|
-
const captionsData = captions?.unifiedCaptionsDataTask?.value;
|
|
104
|
-
let shouldUpdate = false;
|
|
105
|
-
if (captionsData) {
|
|
106
|
-
const captionsLocalTimeSec = (currentTimeMs - captions.startTimeMs) / 1e3;
|
|
107
|
-
const activeWordIndex = captionsData.word_segments.findIndex((word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end);
|
|
108
|
-
const activeSegmentIndex = captionsData.segments.findIndex((seg) => captionsLocalTimeSec >= seg.start && captionsLocalTimeSec < seg.end);
|
|
109
|
-
if (activeWordIndex !== this.lastActiveWordIndex) {
|
|
110
|
-
this.lastActiveWordIndex = activeWordIndex;
|
|
111
|
-
shouldUpdate = true;
|
|
112
|
-
}
|
|
113
|
-
if (activeSegmentIndex !== this.lastActiveSegmentIndex) {
|
|
114
|
-
this.lastActiveSegmentIndex = activeSegmentIndex;
|
|
115
|
-
shouldUpdate = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (Math.abs(currentTimeMs - this.lastTimeMs) >= CaptionsTimeController.MIN_TIME_CHANGE_MS) shouldUpdate = true;
|
|
119
|
-
if (shouldUpdate) {
|
|
120
|
-
this.lastTimeMs = currentTimeMs;
|
|
121
|
-
this.host.requestUpdate();
|
|
122
|
-
}
|
|
123
|
-
this.animationFrameId = requestAnimationFrame(update);
|
|
124
|
-
};
|
|
125
|
-
update();
|
|
126
|
-
}
|
|
127
|
-
stopTimeUpdate() {
|
|
128
|
-
if (this.animationFrameId) {
|
|
129
|
-
cancelAnimationFrame(this.animationFrameId);
|
|
130
|
-
this.animationFrameId = void 0;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
77
|
let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
135
78
|
constructor(..._args) {
|
|
136
79
|
super(..._args);
|
|
137
80
|
this.contextCurrentTimeMs = 0;
|
|
138
|
-
this._timeController = new CaptionsTimeController(this);
|
|
139
81
|
this.lastPixelsPerMs = 0;
|
|
140
82
|
}
|
|
141
83
|
static {
|
|
@@ -164,13 +106,13 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
164
106
|
padding: 2px 4px;
|
|
165
107
|
border-radius: 2px;
|
|
166
108
|
transition: all 0.1s ease;
|
|
167
|
-
background:
|
|
168
|
-
color:
|
|
109
|
+
background: var(--ef-color-bg-elevated);
|
|
110
|
+
color: var(--ef-color-text);
|
|
169
111
|
z-index: 1;
|
|
170
112
|
}
|
|
171
113
|
|
|
172
114
|
.word-element.active {
|
|
173
|
-
background:
|
|
115
|
+
background: var(--ef-color-success);
|
|
174
116
|
color: rgb(20, 30, 20);
|
|
175
117
|
font-weight: 700;
|
|
176
118
|
font-size: 10px;
|
|
@@ -179,14 +121,14 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
179
121
|
}
|
|
180
122
|
|
|
181
123
|
.word-element.future {
|
|
182
|
-
background:
|
|
183
|
-
color:
|
|
124
|
+
background: var(--ef-color-bg-inset);
|
|
125
|
+
color: var(--ef-color-text);
|
|
184
126
|
z-index: 5;
|
|
185
127
|
}
|
|
186
128
|
|
|
187
129
|
.segment-block.active .word-element:not(.active):not(.future) {
|
|
188
|
-
color:
|
|
189
|
-
background:
|
|
130
|
+
color: var(--ef-color-text-muted);
|
|
131
|
+
background: var(--ef-color-bg-elevated);
|
|
190
132
|
}
|
|
191
133
|
|
|
192
134
|
/* Compact text mode - when words are too small to position individually */
|
|
@@ -205,7 +147,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
205
147
|
/* Expand to fit content on hover */
|
|
206
148
|
width: max-content !important;
|
|
207
149
|
min-width: max-content;
|
|
208
|
-
background:
|
|
150
|
+
background: var(--ef-color-bg-elevated) !important;
|
|
209
151
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
210
152
|
}
|
|
211
153
|
|
|
@@ -215,7 +157,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
215
157
|
white-space: nowrap;
|
|
216
158
|
overflow: hidden;
|
|
217
159
|
text-overflow: ellipsis;
|
|
218
|
-
color:
|
|
160
|
+
color: var(--ef-color-text);
|
|
219
161
|
width: 100%;
|
|
220
162
|
}
|
|
221
163
|
|
|
@@ -226,7 +168,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
226
168
|
}
|
|
227
169
|
|
|
228
170
|
.segment-block.compact-text.active .segment-text-compact {
|
|
229
|
-
color:
|
|
171
|
+
color: var(--ef-color-text-muted);
|
|
230
172
|
font-weight: 500;
|
|
231
173
|
}
|
|
232
174
|
|
|
@@ -251,12 +193,12 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
251
193
|
bottom: 0;
|
|
252
194
|
width: 1px;
|
|
253
195
|
height: 30%;
|
|
254
|
-
background:
|
|
196
|
+
background: var(--ef-color-border-subtle);
|
|
255
197
|
pointer-events: none;
|
|
256
198
|
}
|
|
257
199
|
|
|
258
200
|
.word-marker.active {
|
|
259
|
-
background:
|
|
201
|
+
background: var(--ef-color-text);
|
|
260
202
|
height: 50%;
|
|
261
203
|
width: 2px;
|
|
262
204
|
}
|
|
@@ -318,7 +260,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
318
260
|
const captions = this.element;
|
|
319
261
|
const rootTimegroup = captions.rootTimegroup;
|
|
320
262
|
const captionsLocalTimeSec = ((this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0) - captions.startTimeMs) / 1e3;
|
|
321
|
-
const captionColor = "
|
|
263
|
+
const captionColor = getElementTypeColor("captions", this);
|
|
322
264
|
const activeWord = captionsData.word_segments.find((word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end);
|
|
323
265
|
return html`
|
|
324
266
|
${captionsData.word_segments.map((word) => {
|
|
@@ -399,8 +341,8 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
|
|
|
399
341
|
width: `${Math.max(segmentWidth, 4)}px`,
|
|
400
342
|
height: "100%",
|
|
401
343
|
top: "0px",
|
|
402
|
-
backgroundColor: isActiveSegment ? `
|
|
403
|
-
borderColor: isActiveSegment ? captionColor : `
|
|
344
|
+
backgroundColor: isActiveSegment ? `color-mix(in srgb, var(--ef-color-type-captions) ${30 + density * 20}%, transparent)` : `color-mix(in srgb, var(--ef-color-type-captions) ${10 + density * 10}%, transparent)`,
|
|
345
|
+
borderColor: isActiveSegment ? captionColor : `color-mix(in srgb, var(--ef-color-type-captions) 40%, transparent)`,
|
|
404
346
|
minWidth: segmentWidth < 20 ? "20px" : "auto"
|
|
405
347
|
})}
|
|
406
348
|
title=${useCompactText ? `Caption: '${segment.text}'\nDuration: ${this.formatDuration(segmentDuration)}\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s` : `Caption: '${segment.text}'\nDuration: ${this.formatDuration(segmentDuration)}\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\nWords: ${wordsInSegment.length}`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CaptionsTrack.js","names":["measurementCanvas: HTMLCanvasElement | null","measurementContext: CanvasRenderingContext2D | null","wordWidths: Array<{ textWidth: number; timeWidth: number; startPx: number; endPx: number }>","host: EFCaptionsTrack","EFCaptionsTrack","EFCaptionsActiveWordTrack","EFCaptionsSegmentTrack","EFCaptionsBeforeWordTrack","EFCaptionsAfterWordTrack"],"sources":["../../../../src/gui/timeline/tracks/CaptionsTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport type { ReactiveController } from \"lit\";\nimport {\n type Caption,\n EFCaptions,\n type WordSegment,\n} from \"../../../elements/EFCaptions.js\";\nimport { phosphorIcon, ICONS } from \"../../icons.js\";\nimport { currentTimeContext } from \"../../currentTimeContext.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\n\n// Shared canvas context for text measurement (avoids creating new canvas each time)\nlet measurementCanvas: HTMLCanvasElement | null = null;\nlet measurementContext: CanvasRenderingContext2D | null = null;\n// Cache for text measurements: key is \"text:fontSize:fontWeight\"\nconst textMeasurementCache = new Map<string, number>();\nconst MAX_CACHE_SIZE = 500;\n\n/**\n * Measure text width accurately using canvas.\n * Matches the actual font used in word elements (font-weight: 500).\n * Results are cached to avoid repeated measurements of the same text.\n */\nfunction measureTextWidth(text: string, fontSize: number, fontWeight: number = 500): number {\n // Check cache first\n const cacheKey = `${text}:${fontSize}:${fontWeight}`;\n const cached = textMeasurementCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n \n // Initialize shared canvas context if needed\n if (!measurementCanvas || !measurementContext) {\n measurementCanvas = document.createElement(\"canvas\");\n measurementContext = measurementCanvas.getContext(\"2d\");\n }\n \n if (!measurementContext) {\n return text.length * fontSize * 0.6; // Fallback estimate\n }\n \n // Match the actual font used in word elements\n const fontFamily = getComputedStyle(document.body).fontFamily || \"system-ui, sans-serif\";\n measurementContext.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n const width = measurementContext.measureText(text).width;\n \n // Cache the result (with size limit to prevent memory leaks)\n if (textMeasurementCache.size >= MAX_CACHE_SIZE) {\n // Clear oldest entries (simple strategy: clear half the cache)\n const keysToDelete = Array.from(textMeasurementCache.keys()).slice(0, MAX_CACHE_SIZE / 2);\n for (const key of keysToDelete) {\n textMeasurementCache.delete(key);\n }\n }\n textMeasurementCache.set(cacheKey, width);\n \n return width;\n}\n\n/**\n * Check if words can fit individually within a segment when positioned by time\n * \n * Strategy: Allow overlaps as long as all words can be rendered within the container.\n * Only use compact mode when words are so cramped they can't be displayed at all.\n */\nfunction canWordsFitIndividually(\n words: WordSegment[],\n segmentStart: number,\n segmentWidthPx: number,\n pixelsPerMs: number,\n): { fits: boolean; reason?: string } {\n if (words.length === 0) {\n return { fits: false, reason: \"no words\" };\n }\n \n // Measure total text width of all words (as if rendered sequentially)\n let totalTextWidth = 0;\n const wordWidths: Array<{ textWidth: number; timeWidth: number; startPx: number; endPx: number }> = [];\n \n for (const word of words) {\n if (!word) continue;\n \n // Measure actual text width (with padding: 2px left + 2px right = 4px total)\n const textWidth = measureTextWidth(word.text.trim(), 9, 500) + 4;\n \n // Calculate time-based position and width\n const startPx = pixelsPerMs * (word.start - segmentStart) * 1000;\n const endPx = pixelsPerMs * (word.end - segmentStart) * 1000;\n const timeWidth = endPx - startPx;\n \n wordWidths.push({ textWidth, timeWidth, startPx, endPx });\n totalTextWidth += textWidth;\n }\n \n // Key insight: If total text width fits in segment, we can render words individually\n // even if they overlap based on their time positions\n // Use 90% threshold to account for some spacing/overlap\n if (totalTextWidth <= segmentWidthPx * 0.9) {\n // All words can fit - use positioned mode (overlaps are okay)\n return { fits: true };\n }\n \n // If total text doesn't fit, check if individual words are too narrow to be readable\n // If any word's time-based width is less than 30% of its text width, it's unreadable\n for (const { textWidth, timeWidth } of wordWidths) {\n if (timeWidth < textWidth * 0.3) {\n return { fits: false, reason: `word too narrow (${timeWidth.toFixed(1)}px < ${(textWidth * 0.3).toFixed(1)}px)` };\n }\n }\n \n // If words are readable individually but total text is too wide,\n // check if they can still fit with overlaps\n // Find the maximum right edge of all words\n const maxEndPx = Math.max(...wordWidths.map(w => w.endPx));\n \n // If the rightmost word fits within the segment, allow overlaps\n if (maxEndPx <= segmentWidthPx * 1.1) {\n return { fits: true };\n }\n \n // Words don't fit - use compact mode\n return { fits: false, reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)` };\n}\n\n/**\n * Controller to ensure captions track updates reactively during playback.\n * \n * Performance optimization: Only requests updates when the visual state actually\n * needs to change (active word/segment changed), not on every frame.\n */\nclass CaptionsTimeController implements ReactiveController {\n private animationFrameId?: number;\n private lastTimeMs = -1;\n private lastActiveWordIndex = -1;\n private lastActiveSegmentIndex = -1;\n // Minimum time change to trigger update when no word change (for segment boundaries)\n private static readonly MIN_TIME_CHANGE_MS = 100;\n \n constructor(private host: EFCaptionsTrack) {\n this.host.addController(this);\n }\n \n hostConnected(): void {\n this.startTimeUpdate();\n }\n \n hostDisconnected(): void {\n this.stopTimeUpdate();\n }\n \n private startTimeUpdate(): void {\n const update = () => {\n // Read current time from root timegroup\n const captions = this.host.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsData = captions?.unifiedCaptionsDataTask?.value;\n \n // Check if we actually need to update\n let shouldUpdate = false;\n \n if (captionsData) {\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n \n // Find current active word and segment indices\n const activeWordIndex = captionsData.word_segments.findIndex(\n (word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end\n );\n const activeSegmentIndex = captionsData.segments.findIndex(\n (seg) => captionsLocalTimeSec >= seg.start && captionsLocalTimeSec < seg.end\n );\n \n // Update if active word or segment changed\n if (activeWordIndex !== this.lastActiveWordIndex) {\n this.lastActiveWordIndex = activeWordIndex;\n shouldUpdate = true;\n }\n if (activeSegmentIndex !== this.lastActiveSegmentIndex) {\n this.lastActiveSegmentIndex = activeSegmentIndex;\n shouldUpdate = true;\n }\n }\n \n // Also update if time changed significantly (for visual feedback during seek)\n const timeDelta = Math.abs(currentTimeMs - this.lastTimeMs);\n if (timeDelta >= CaptionsTimeController.MIN_TIME_CHANGE_MS) {\n shouldUpdate = true;\n }\n \n if (shouldUpdate) {\n this.lastTimeMs = currentTimeMs;\n this.host.requestUpdate();\n }\n \n this.animationFrameId = requestAnimationFrame(update);\n };\n update();\n }\n \n private stopTimeUpdate(): void {\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n }\n}\n\n@customElement(\"ef-captions-track\")\nexport class EFCaptionsTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .segment-block {\n position: absolute;\n border-radius: 3px;\n transition: box-shadow 0.15s ease, z-index 0.15s ease;\n cursor: pointer;\n overflow: visible;\n }\n \n .segment-block:hover {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n z-index: 5;\n }\n \n .word-element {\n position: absolute;\n font-size: 9px;\n line-height: 1.2;\n white-space: nowrap;\n font-weight: 500;\n top: 50%;\n transform: translateY(-50%);\n padding: 2px 4px;\n border-radius: 2px;\n transition: all 0.1s ease;\n background: rgba(30, 41, 59, 0.9);\n color: rgb(226, 232, 240);\n z-index: 1;\n }\n \n .word-element.active {\n background: rgb(74, 222, 128);\n color: rgb(20, 30, 20);\n font-weight: 700;\n font-size: 10px;\n z-index: 10;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n }\n \n .word-element.future {\n background: rgba(51, 65, 85, 0.8);\n color: rgb(226, 232, 240);\n z-index: 5;\n }\n \n .segment-block.active .word-element:not(.active):not(.future) {\n color: rgb(203, 213, 225);\n background: rgba(30, 41, 59, 0.9);\n }\n \n /* Compact text mode - when words are too small to position individually */\n .segment-block.compact-text {\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n /* Keep position: absolute from .segment-block for correct time-based positioning */\n }\n \n /* Allow overflow on hover for compact text */\n .segment-block.compact-text:hover {\n overflow: visible;\n z-index: 100;\n /* Expand to fit content on hover */\n width: max-content !important;\n min-width: max-content;\n background: rgba(34, 60, 40, 0.95) !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text-compact {\n font-size: 10px;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: rgb(226, 232, 240);\n width: 100%;\n }\n \n /* On hover, show full text */\n .segment-block.compact-text:hover .segment-text-compact {\n overflow: visible;\n text-overflow: clip;\n }\n \n .segment-block.compact-text.active .segment-text-compact {\n color: rgb(203, 213, 225);\n font-weight: 500;\n }\n \n .segment-duration-indicator {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 2px;\n background: currentColor;\n opacity: 0.2;\n border-radius: 0 0 3px 3px;\n }\n \n .segment-block.active .segment-duration-indicator {\n opacity: 0.4;\n height: 2px;\n }\n \n .word-marker {\n position: absolute;\n bottom: 0;\n width: 1px;\n height: 30%;\n background: rgba(255, 255, 255, 0.3);\n pointer-events: none;\n }\n \n .word-marker.active {\n background: rgba(255, 255, 255, 0.8);\n height: 50%;\n width: 2px;\n }\n `,\n ];\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = 0;\n \n // Controller ensures real-time updates during playback\n // The controller manages its own lifecycle via ReactiveController interface\n private _timeController = new CaptionsTimeController(this);\n \n private lastPixelsPerMs = 0;\n \n protected updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated(changedProperties);\n \n // Re-render when pixelsPerMs changes (zoom level changes)\n if (changedProperties.has(\"pixelsPerMs\")) {\n const currentPixelsPerMs = this.pixelsPerMs;\n if (currentPixelsPerMs !== this.lastPixelsPerMs) {\n this.lastPixelsPerMs = currentPixelsPerMs;\n // Force update to recalculate layout mode\n this.requestUpdate();\n }\n }\n }\n\n render() {\n const captions = this.element as EFCaptions;\n const captionsData = captions.unifiedCaptionsDataTask.value;\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: \"var(--timeline-track-height, 22px)\",\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n minHeight: \"22px\",\n })}\n >\n ${this.renderCaptionsData(captionsData)}\n ${\n this.enableTrim\n ? html`<ef-trim-handles\n element-id=${(this.element as HTMLElement).id || \"\"}\n pixels-per-ms=${this.pixelsPerMs}\n trim-start-ms=${this.element.trimStartMs ?? 0}\n trim-end-ms=${this.element.trimEndMs ?? 0}\n intrinsic-duration-ms=${this.element.intrinsicDurationMs ?? this.element.durationMs}\n @trim-change=${this.handleTrimChange}\n ></ef-trim-handles>`\n : nothing\n }\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n \n\n renderCaptionsData(captionsData: Caption | null | undefined) {\n if (!captionsData) {\n return html``;\n }\n\n const captions = this.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n // Use context current time for reactivity, fallback to rootTimegroup\n const currentTimeMs = this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n // Get element type color for captions\n const captionColor = \"rgb(34, 197, 94)\"; // Green for captions\n \n // Find active word for highlighting\n const activeWord = captionsData.word_segments.find(\n (word) =>\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end\n );\n \n // Render word markers for visual density indication (subtle)\n const wordMarkers = captionsData.word_segments.map((word) => {\n const wordStartPx = this.pixelsPerMs * word.start * 1000;\n const wordWidth = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n \n // Only show markers if they're wide enough to be visible\n if (wordWidth < 1.5) return nothing;\n \n return html`<div\n class=\"word-marker ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${wordStartPx}px`,\n })}\n ></div>`;\n });\n\n // Render semantic segment blocks with words positioned by their actual timing\n const segmentElements = captionsData.segments.map((segment) => {\n const isActiveSegment =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n \n const segmentStartPx = this.pixelsPerMs * segment.start * 1000;\n const segmentWidth = this.pixelsPerMs * (segment.end - segment.start) * 1000;\n const segmentDuration = (segment.end - segment.start) * 1000;\n \n // Get words in this segment, sorted by start time\n const wordsInSegment = captionsData.word_segments\n .filter(\n (word) =>\n word.start >= segment.start && word.end <= segment.end\n )\n .sort((a, b) => a.start - b.start);\n \n // Calculate visual density based on word count\n const density = Math.min(wordsInSegment.length / 10, 1);\n \n // Use actual measurement to determine if words can fit individually\n // Allow overlaps - only use compact mode when words can't be rendered at all\n const measurementResult = canWordsFitIndividually(\n wordsInSegment,\n segment.start,\n segmentWidth,\n this.pixelsPerMs,\n );\n \n const useCompactText = !measurementResult.fits;\n let avgSpacing = 0;\n \n // Calculate average spacing for font scaling (only if using positioned mode)\n if (!useCompactText && wordsInSegment.length > 1) {\n let totalSpacing = 0;\n let spacingCount = 0;\n \n for (let i = 0; i < wordsInSegment.length - 1; i++) {\n const word1 = wordsInSegment[i];\n const word2 = wordsInSegment[i + 1];\n if (!word1 || !word2) continue;\n \n const word1EndPx = this.pixelsPerMs * (word1.end - segment.start) * 1000;\n const word2StartPx = this.pixelsPerMs * (word2.start - segment.start) * 1000;\n const spacing = word2StartPx - word1EndPx;\n \n if (spacing > 0) {\n totalSpacing += spacing;\n spacingCount++;\n }\n }\n \n avgSpacing = spacingCount > 0 ? totalSpacing / spacingCount : 0;\n }\n \n // Calculate optimal font size for positioned words (if not using compact mode)\n const MIN_READABLE_FONT_SIZE = 6; // Minimum readable font size in pixels\n const baseFontSize = 9;\n const activeFontSize = 10;\n let scaledFontSize = baseFontSize;\n let scaledActiveFontSize = activeFontSize;\n \n if (!useCompactText && wordsInSegment.length > 1 && avgSpacing < 8) {\n // Scale down font size proportionally, but don't go below minimum\n const scaleFactor = Math.max(MIN_READABLE_FONT_SIZE / baseFontSize, avgSpacing / 8);\n scaledFontSize = Math.max(MIN_READABLE_FONT_SIZE, baseFontSize * scaleFactor);\n scaledActiveFontSize = Math.max(MIN_READABLE_FONT_SIZE, activeFontSize * scaleFactor);\n }\n \n // Render words positioned by their actual timing within the segment\n const renderWords = () => {\n if (useCompactText) {\n // Compact mode: show text that can overflow on hover\n return html`\n <span class=\"segment-text-compact\">${segment.text}</span>\n `;\n }\n \n // Positioned mode: render words at their time positions\n return wordsInSegment.map((word) => {\n // Position relative to segment start\n const wordOffsetFromSegmentStart = (word.start - segment.start) * 1000;\n const wordLeftPx = this.pixelsPerMs * wordOffsetFromSegmentStart;\n const wordWidthPx = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n \n // Determine if word is in the future (after active word)\n const isFuture = activeWord && word.start > activeWord.end;\n \n return html`\n <span\n class=\"word-element ${isActive ? \"active\" : \"\"} ${isFuture ? \"future\" : \"\"}\"\n style=${styleMap({\n left: `${wordLeftPx}px`,\n minWidth: `${Math.max(wordWidthPx, 8)}px`,\n fontSize: isActive ? `${scaledActiveFontSize}px` : `${scaledFontSize}px`,\n top: \"50%\",\n })}\n title=\"Word: '${word.text}' (${word.start.toFixed(2)}s - ${word.end.toFixed(2)}s)\"\n >\n ${word.text.trim()}\n </span>\n `;\n });\n };\n \n return html`<div\n class=\"segment-block ${isActiveSegment ? \"active\" : \"\"} ${useCompactText ? \"compact-text\" : \"\"}\"\n style=${styleMap({\n left: `${segmentStartPx}px`,\n width: `${Math.max(segmentWidth, 4)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isActiveSegment\n ? `rgba(34, 197, 94, ${0.3 + density * 0.2})`\n : `rgba(34, 197, 94, ${0.1 + density * 0.1})`,\n borderColor: isActiveSegment\n ? captionColor\n : `rgba(34, 197, 94, 0.4)`,\n minWidth: segmentWidth < 20 ? \"20px\" : \"auto\",\n })}\n title=${useCompactText \n ? `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s`\n : `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\\nWords: ${wordsInSegment.length}`}\n @click=${(e: MouseEvent) => {\n e.stopPropagation();\n // Affordance: Click to seek to segment start\n if (rootTimegroup) {\n const absoluteStartTime = captions.startTimeMs + segment.start * 1000;\n rootTimegroup.currentTimeMs = absoluteStartTime;\n }\n }}\n >\n ${renderWords()}\n ${!useCompactText ? html`<div class=\"segment-duration-indicator\"></div>` : nothing}\n </div>`;\n });\n\n return html`\n ${wordMarkers}\n ${segmentElements}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n // Don't render child tracks - captions are consolidated into a single track\n // Child elements (active-word, segment, before-word, after-word) are handled\n // inline within the main captions track visualization\n return nothing;\n }\n}\n\n@customElement(\"ef-captions-active-word-track\")\nexport class EFCaptionsActiveWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.microphone)} Active Word\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${word.text.trim()}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-segment-track\")\nexport class EFCaptionsSegmentTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.textT)} Segment\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.segments.map((segment) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.start * 1000}px`,\n width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-segment-bg);\">${segment.text}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-before-word-track\")\nexport class EFCaptionsBeforeWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowLeft)} Before\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-after-word-track\")\nexport class EFCaptionsAfterWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowRight)} After\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions-track\": EFCaptionsTrack;\n \"ef-captions-active-word-track\": EFCaptionsActiveWordTrack;\n \"ef-captions-segment-track\": EFCaptionsSegmentTrack;\n \"ef-captions-before-word-track\": EFCaptionsBeforeWordTrack;\n \"ef-captions-after-word-track\": EFCaptionsAfterWordTrack;\n }\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CaptionsTrack.js","names":["measurementCanvas: HTMLCanvasElement | null","measurementContext: CanvasRenderingContext2D | null","wordWidths: Array<{\n textWidth: number;\n timeWidth: number;\n startPx: number;\n endPx: number;\n }>","EFCaptionsTrack","EFCaptionsActiveWordTrack","EFCaptionsSegmentTrack","EFCaptionsBeforeWordTrack","EFCaptionsAfterWordTrack"],"sources":["../../../../src/gui/timeline/tracks/CaptionsTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport {\n type Caption,\n EFCaptions,\n type WordSegment,\n} from \"../../../elements/EFCaptions.js\";\nimport { phosphorIcon, ICONS } from \"../../icons.js\";\nimport { currentTimeContext } from \"../../currentTimeContext.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { getElementTypeColor } from \"../../theme.js\";\n\n// Shared canvas context for text measurement (avoids creating new canvas each time)\nlet measurementCanvas: HTMLCanvasElement | null = null;\nlet measurementContext: CanvasRenderingContext2D | null = null;\n// Cache for text measurements: key is \"text:fontSize:fontWeight\"\nconst textMeasurementCache = new Map<string, number>();\nconst MAX_CACHE_SIZE = 500;\n\n/**\n * Measure text width accurately using canvas.\n * Matches the actual font used in word elements (font-weight: 500).\n * Results are cached to avoid repeated measurements of the same text.\n */\nfunction measureTextWidth(\n text: string,\n fontSize: number,\n fontWeight: number = 500,\n): number {\n // Check cache first\n const cacheKey = `${text}:${fontSize}:${fontWeight}`;\n const cached = textMeasurementCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n // Initialize shared canvas context if needed\n if (!measurementCanvas || !measurementContext) {\n measurementCanvas = document.createElement(\"canvas\");\n measurementContext = measurementCanvas.getContext(\"2d\");\n }\n\n if (!measurementContext) {\n return text.length * fontSize * 0.6; // Fallback estimate\n }\n\n // Match the actual font used in word elements\n const fontFamily =\n getComputedStyle(document.body).fontFamily || \"system-ui, sans-serif\";\n measurementContext.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n const width = measurementContext.measureText(text).width;\n\n // Cache the result (with size limit to prevent memory leaks)\n if (textMeasurementCache.size >= MAX_CACHE_SIZE) {\n // Clear oldest entries (simple strategy: clear half the cache)\n const keysToDelete = Array.from(textMeasurementCache.keys()).slice(\n 0,\n MAX_CACHE_SIZE / 2,\n );\n for (const key of keysToDelete) {\n textMeasurementCache.delete(key);\n }\n }\n textMeasurementCache.set(cacheKey, width);\n\n return width;\n}\n\n/**\n * Check if words can fit individually within a segment when positioned by time\n *\n * Strategy: Allow overlaps as long as all words can be rendered within the container.\n * Only use compact mode when words are so cramped they can't be displayed at all.\n */\nfunction canWordsFitIndividually(\n words: WordSegment[],\n segmentStart: number,\n segmentWidthPx: number,\n pixelsPerMs: number,\n): { fits: boolean; reason?: string } {\n if (words.length === 0) {\n return { fits: false, reason: \"no words\" };\n }\n\n // Measure total text width of all words (as if rendered sequentially)\n let totalTextWidth = 0;\n const wordWidths: Array<{\n textWidth: number;\n timeWidth: number;\n startPx: number;\n endPx: number;\n }> = [];\n\n for (const word of words) {\n if (!word) continue;\n\n // Measure actual text width (with padding: 2px left + 2px right = 4px total)\n const textWidth = measureTextWidth(word.text.trim(), 9, 500) + 4;\n\n // Calculate time-based position and width\n const startPx = pixelsPerMs * (word.start - segmentStart) * 1000;\n const endPx = pixelsPerMs * (word.end - segmentStart) * 1000;\n const timeWidth = endPx - startPx;\n\n wordWidths.push({ textWidth, timeWidth, startPx, endPx });\n totalTextWidth += textWidth;\n }\n\n // Key insight: If total text width fits in segment, we can render words individually\n // even if they overlap based on their time positions\n // Use 90% threshold to account for some spacing/overlap\n if (totalTextWidth <= segmentWidthPx * 0.9) {\n // All words can fit - use positioned mode (overlaps are okay)\n return { fits: true };\n }\n\n // If total text doesn't fit, check if individual words are too narrow to be readable\n // If any word's time-based width is less than 30% of its text width, it's unreadable\n for (const { textWidth, timeWidth } of wordWidths) {\n if (timeWidth < textWidth * 0.3) {\n return {\n fits: false,\n reason: `word too narrow (${timeWidth.toFixed(1)}px < ${(textWidth * 0.3).toFixed(1)}px)`,\n };\n }\n }\n\n // If words are readable individually but total text is too wide,\n // check if they can still fit with overlaps\n // Find the maximum right edge of all words\n const maxEndPx = Math.max(...wordWidths.map((w) => w.endPx));\n\n // If the rightmost word fits within the segment, allow overlaps\n if (maxEndPx <= segmentWidthPx * 1.1) {\n return { fits: true };\n }\n\n // Words don't fit - use compact mode\n return {\n fits: false,\n reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)`,\n };\n}\n\n@customElement(\"ef-captions-track\")\nexport class EFCaptionsTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .segment-block {\n position: absolute;\n border-radius: 3px;\n transition: box-shadow 0.15s ease, z-index 0.15s ease;\n cursor: pointer;\n overflow: visible;\n }\n \n .segment-block:hover {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n z-index: 5;\n }\n \n .word-element {\n position: absolute;\n font-size: 9px;\n line-height: 1.2;\n white-space: nowrap;\n font-weight: 500;\n top: 50%;\n transform: translateY(-50%);\n padding: 2px 4px;\n border-radius: 2px;\n transition: all 0.1s ease;\n background: var(--ef-color-bg-elevated);\n color: var(--ef-color-text);\n z-index: 1;\n }\n \n .word-element.active {\n background: var(--ef-color-success);\n color: rgb(20, 30, 20);\n font-weight: 700;\n font-size: 10px;\n z-index: 10;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n }\n \n .word-element.future {\n background: var(--ef-color-bg-inset);\n color: var(--ef-color-text);\n z-index: 5;\n }\n \n .segment-block.active .word-element:not(.active):not(.future) {\n color: var(--ef-color-text-muted);\n background: var(--ef-color-bg-elevated);\n }\n \n /* Compact text mode - when words are too small to position individually */\n .segment-block.compact-text {\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n /* Keep position: absolute from .segment-block for correct time-based positioning */\n }\n \n /* Allow overflow on hover for compact text */\n .segment-block.compact-text:hover {\n overflow: visible;\n z-index: 100;\n /* Expand to fit content on hover */\n width: max-content !important;\n min-width: max-content;\n background: var(--ef-color-bg-elevated) !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text-compact {\n font-size: 10px;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--ef-color-text);\n width: 100%;\n }\n \n /* On hover, show full text */\n .segment-block.compact-text:hover .segment-text-compact {\n overflow: visible;\n text-overflow: clip;\n }\n \n .segment-block.compact-text.active .segment-text-compact {\n color: var(--ef-color-text-muted);\n font-weight: 500;\n }\n \n .segment-duration-indicator {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 2px;\n background: currentColor;\n opacity: 0.2;\n border-radius: 0 0 3px 3px;\n }\n \n .segment-block.active .segment-duration-indicator {\n opacity: 0.4;\n height: 2px;\n }\n \n .word-marker {\n position: absolute;\n bottom: 0;\n width: 1px;\n height: 30%;\n background: var(--ef-color-border-subtle);\n pointer-events: none;\n }\n \n .word-marker.active {\n background: var(--ef-color-text);\n height: 50%;\n width: 2px;\n }\n `,\n ];\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = 0;\n\n private lastPixelsPerMs = 0;\n\n protected updated(\n changedProperties: Map<string | number | symbol, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Re-render when pixelsPerMs changes (zoom level changes)\n if (changedProperties.has(\"pixelsPerMs\")) {\n const currentPixelsPerMs = this.pixelsPerMs;\n if (currentPixelsPerMs !== this.lastPixelsPerMs) {\n this.lastPixelsPerMs = currentPixelsPerMs;\n // Force update to recalculate layout mode\n this.requestUpdate();\n }\n }\n }\n\n render() {\n const captions = this.element as EFCaptions;\n const captionsData = captions.unifiedCaptionsDataTask.value;\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: \"var(--timeline-track-height, 22px)\",\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n minHeight: \"22px\",\n })}\n >\n ${this.renderCaptionsData(captionsData)}\n ${\n this.enableTrim\n ? html`<ef-trim-handles\n element-id=${(this.element as HTMLElement).id || \"\"}\n pixels-per-ms=${this.pixelsPerMs}\n trim-start-ms=${this.element.trimStartMs ?? 0}\n trim-end-ms=${this.element.trimEndMs ?? 0}\n intrinsic-duration-ms=${this.element.intrinsicDurationMs ?? this.element.durationMs}\n @trim-change=${this.handleTrimChange}\n ></ef-trim-handles>`\n : nothing\n }\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n\n renderCaptionsData(captionsData: Caption | null | undefined) {\n if (!captionsData) {\n return html``;\n }\n\n const captions = this.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n // Use context current time for reactivity, fallback to rootTimegroup\n const currentTimeMs =\n this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n // Get element type color for captions using shared theme utility\n const captionColor = getElementTypeColor(\"captions\", this);\n\n // Find active word for highlighting\n const activeWord = captionsData.word_segments.find(\n (word) =>\n captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end,\n );\n\n // Render word markers for visual density indication (subtle)\n const wordMarkers = captionsData.word_segments.map((word) => {\n const wordStartPx = this.pixelsPerMs * word.start * 1000;\n const wordWidth = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n\n // Only show markers if they're wide enough to be visible\n if (wordWidth < 1.5) return nothing;\n\n return html`<div\n class=\"word-marker ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${wordStartPx}px`,\n })}\n ></div>`;\n });\n\n // Render semantic segment blocks with words positioned by their actual timing\n const segmentElements = captionsData.segments.map((segment) => {\n const isActiveSegment =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n const segmentStartPx = this.pixelsPerMs * segment.start * 1000;\n const segmentWidth =\n this.pixelsPerMs * (segment.end - segment.start) * 1000;\n const segmentDuration = (segment.end - segment.start) * 1000;\n\n // Get words in this segment, sorted by start time\n const wordsInSegment = captionsData.word_segments\n .filter(\n (word) => word.start >= segment.start && word.end <= segment.end,\n )\n .sort((a, b) => a.start - b.start);\n\n // Calculate visual density based on word count\n const density = Math.min(wordsInSegment.length / 10, 1);\n\n // Use actual measurement to determine if words can fit individually\n // Allow overlaps - only use compact mode when words can't be rendered at all\n const measurementResult = canWordsFitIndividually(\n wordsInSegment,\n segment.start,\n segmentWidth,\n this.pixelsPerMs,\n );\n\n const useCompactText = !measurementResult.fits;\n let avgSpacing = 0;\n\n // Calculate average spacing for font scaling (only if using positioned mode)\n if (!useCompactText && wordsInSegment.length > 1) {\n let totalSpacing = 0;\n let spacingCount = 0;\n\n for (let i = 0; i < wordsInSegment.length - 1; i++) {\n const word1 = wordsInSegment[i];\n const word2 = wordsInSegment[i + 1];\n if (!word1 || !word2) continue;\n\n const word1EndPx =\n this.pixelsPerMs * (word1.end - segment.start) * 1000;\n const word2StartPx =\n this.pixelsPerMs * (word2.start - segment.start) * 1000;\n const spacing = word2StartPx - word1EndPx;\n\n if (spacing > 0) {\n totalSpacing += spacing;\n spacingCount++;\n }\n }\n\n avgSpacing = spacingCount > 0 ? totalSpacing / spacingCount : 0;\n }\n\n // Calculate optimal font size for positioned words (if not using compact mode)\n const MIN_READABLE_FONT_SIZE = 6; // Minimum readable font size in pixels\n const baseFontSize = 9;\n const activeFontSize = 10;\n let scaledFontSize = baseFontSize;\n let scaledActiveFontSize = activeFontSize;\n\n if (!useCompactText && wordsInSegment.length > 1 && avgSpacing < 8) {\n // Scale down font size proportionally, but don't go below minimum\n const scaleFactor = Math.max(\n MIN_READABLE_FONT_SIZE / baseFontSize,\n avgSpacing / 8,\n );\n scaledFontSize = Math.max(\n MIN_READABLE_FONT_SIZE,\n baseFontSize * scaleFactor,\n );\n scaledActiveFontSize = Math.max(\n MIN_READABLE_FONT_SIZE,\n activeFontSize * scaleFactor,\n );\n }\n\n // Render words positioned by their actual timing within the segment\n const renderWords = () => {\n if (useCompactText) {\n // Compact mode: show text that can overflow on hover\n return html`\n <span class=\"segment-text-compact\">${segment.text}</span>\n `;\n }\n\n // Positioned mode: render words at their time positions\n return wordsInSegment.map((word) => {\n // Position relative to segment start\n const wordOffsetFromSegmentStart =\n (word.start - segment.start) * 1000;\n const wordLeftPx = this.pixelsPerMs * wordOffsetFromSegmentStart;\n const wordWidthPx = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n\n // Determine if word is in the future (after active word)\n const isFuture = activeWord && word.start > activeWord.end;\n\n return html`\n <span\n class=\"word-element ${isActive ? \"active\" : \"\"} ${isFuture ? \"future\" : \"\"}\"\n style=${styleMap({\n left: `${wordLeftPx}px`,\n minWidth: `${Math.max(wordWidthPx, 8)}px`,\n fontSize: isActive\n ? `${scaledActiveFontSize}px`\n : `${scaledFontSize}px`,\n top: \"50%\",\n })}\n title=\"Word: '${word.text}' (${word.start.toFixed(2)}s - ${word.end.toFixed(2)}s)\"\n >\n ${word.text.trim()}\n </span>\n `;\n });\n };\n\n return html`<div\n class=\"segment-block ${isActiveSegment ? \"active\" : \"\"} ${useCompactText ? \"compact-text\" : \"\"}\"\n style=${styleMap({\n left: `${segmentStartPx}px`,\n width: `${Math.max(segmentWidth, 4)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isActiveSegment\n ? `color-mix(in srgb, var(--ef-color-type-captions) ${30 + density * 20}%, transparent)`\n : `color-mix(in srgb, var(--ef-color-type-captions) ${10 + density * 10}%, transparent)`,\n borderColor: isActiveSegment\n ? captionColor\n : `color-mix(in srgb, var(--ef-color-type-captions) 40%, transparent)`,\n minWidth: segmentWidth < 20 ? \"20px\" : \"auto\",\n })}\n title=${\n useCompactText\n ? `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s`\n : `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\\nWords: ${wordsInSegment.length}`\n }\n @click=${(e: MouseEvent) => {\n e.stopPropagation();\n // Affordance: Click to seek to segment start\n if (rootTimegroup) {\n const absoluteStartTime =\n captions.startTimeMs + segment.start * 1000;\n rootTimegroup.currentTimeMs = absoluteStartTime;\n }\n }}\n >\n ${renderWords()}\n ${!useCompactText ? html`<div class=\"segment-duration-indicator\"></div>` : nothing}\n </div>`;\n });\n\n return html`\n ${wordMarkers}\n ${segmentElements}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n // Don't render child tracks - captions are consolidated into a single track\n // Child elements (active-word, segment, before-word, after-word) are handled\n // inline within the main captions track visualization\n return nothing;\n }\n}\n\n@customElement(\"ef-captions-active-word-track\")\nexport class EFCaptionsActiveWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.microphone)} Active Word\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${word.text.trim()}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-segment-track\")\nexport class EFCaptionsSegmentTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.textT)} Segment\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.segments.map((segment) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.start * 1000}px`,\n width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-segment-bg);\">${segment.text}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-before-word-track\")\nexport class EFCaptionsBeforeWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowLeft)} Before\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-after-word-track\")\nexport class EFCaptionsAfterWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowRight)} After\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions-track\": EFCaptionsTrack;\n \"ef-captions-active-word-track\": EFCaptionsActiveWordTrack;\n \"ef-captions-segment-track\": EFCaptionsSegmentTrack;\n \"ef-captions-before-word-track\": EFCaptionsBeforeWordTrack;\n \"ef-captions-after-word-track\": EFCaptionsAfterWordTrack;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TimelineState } from "../timelineStateContext.js";
|
|
2
|
+
import { PreviewSettings } from "../../previewSettingsContext.js";
|
|
3
|
+
import * as lit36 from "lit";
|
|
4
|
+
import { LitElement } from "lit";
|
|
5
|
+
import * as lit_html33 from "lit-html";
|
|
6
|
+
|
|
7
|
+
//#region src/gui/timeline/tracks/EFThumbnailStrip.d.ts
|
|
8
|
+
declare const EFThumbnailStrip_base: typeof LitElement;
|
|
9
|
+
/**
|
|
10
|
+
* Thumbnail strip component that renders thumbnails for video or timegroup elements.
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Targets ef-video or root ef-timegroup via target attribute
|
|
14
|
+
* - Batch video thumbnail extraction via ThumbnailExtractor
|
|
15
|
+
* - Canvas rendering for timegroups at low resolution
|
|
16
|
+
* - Viewport-based lazy loading with scroll calculation
|
|
17
|
+
* - Fixed visual spacing (consistent at all zoom levels)
|
|
18
|
+
* - Error indicators for failed thumbnails
|
|
19
|
+
*/
|
|
20
|
+
declare class EFThumbnailStrip extends EFThumbnailStrip_base {
|
|
21
|
+
#private;
|
|
22
|
+
static styles: lit36.CSSResult[];
|
|
23
|
+
target: string;
|
|
24
|
+
targetElement: Element | null;
|
|
25
|
+
thumbnailHeight: number;
|
|
26
|
+
thumbnailSpacingPx: number;
|
|
27
|
+
pixelsPerMs: number | null;
|
|
28
|
+
useIntrinsicDuration: boolean;
|
|
29
|
+
timelineState?: TimelineState;
|
|
30
|
+
previewSettings?: PreviewSettings;
|
|
31
|
+
thumbnailDimensions: {
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Check if target is valid (EFVideo or root EFTimegroup)
|
|
37
|
+
*/
|
|
38
|
+
get isValidTarget(): boolean;
|
|
39
|
+
connectedCallback(): void;
|
|
40
|
+
disconnectedCallback(): void;
|
|
41
|
+
protected willUpdate(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
42
|
+
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
43
|
+
render(): lit_html33.TemplateResult<1>;
|
|
44
|
+
}
|
|
45
|
+
declare global {
|
|
46
|
+
interface HTMLElementTagNameMap {
|
|
47
|
+
"ef-thumbnail-strip": EFThumbnailStrip;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { EFThumbnailStrip };
|
|
52
|
+
//# sourceMappingURL=EFThumbnailStrip.d.ts.map
|