ugcinc-render 1.3.14 → 1.4.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.mts CHANGED
@@ -457,6 +457,15 @@ interface VideoEditorNodeConfig {
457
457
  previewUrls?: Record<string, string>;
458
458
  previewTextValues?: Record<string, string>;
459
459
  }
460
+ /**
461
+ * Segment position on timeline (calculated from segment timing properties)
462
+ */
463
+ interface SegmentTimelinePosition {
464
+ /** Start time in milliseconds */
465
+ startMs: number;
466
+ /** Duration in milliseconds */
467
+ durationMs: number;
468
+ }
460
469
 
461
470
  /**
462
471
  * Segment types for the rendering system
@@ -1165,6 +1174,68 @@ declare function calculateCropBounds(elements: ImageEditorElement[], dynamicCrop
1165
1174
  */
1166
1175
  declare function isDynamicCropEnabled(dynamicCrop: DynamicCropConfig | undefined): boolean;
1167
1176
 
1177
+ /**
1178
+ * Timeline utility functions for video editor
1179
+ *
1180
+ * These functions calculate segment positions on the timeline based on
1181
+ * the segment/overlay timing model. Used by both the webapp UI and
1182
+ * the VideoEditorComposition for rendering.
1183
+ */
1184
+
1185
+ /**
1186
+ * Create a default TimeValue for segment offsets
1187
+ */
1188
+ declare function defaultOffset(mode?: TimeMode): TimeValue;
1189
+ /**
1190
+ * Get base segments (no parentId) for a channel
1191
+ * Base segments are the primary timeline elements that overlays attach to
1192
+ */
1193
+ declare function getBaseSegments(channel: VideoEditorChannel): VideoEditorSegment[];
1194
+ /**
1195
+ * Get overlays for a specific parent segment (or global overlays if parentId is null)
1196
+ */
1197
+ declare function getOverlays(channel: VideoEditorChannel, parentId: string | null): VideoEditorSegment[];
1198
+ /**
1199
+ * Calculate segment position on timeline
1200
+ *
1201
+ * For base segments:
1202
+ * - Position is calculated from offset and previous segments
1203
+ * - Duration is from the duration property or default (5 seconds)
1204
+ *
1205
+ * For overlay segments:
1206
+ * - Position is calculated relative to parent using relativeStart/relativeEnd
1207
+ * - relativeStart/relativeEnd are fractions (0-1) of parent duration
1208
+ */
1209
+ declare function getSegmentTimelinePosition(segment: VideoEditorSegment, baseSegments: VideoEditorSegment[], channel: VideoEditorChannel): SegmentTimelinePosition;
1210
+ /**
1211
+ * Check if a segment is visible at a given time
1212
+ */
1213
+ declare function isSegmentVisibleAtTime(segment: VideoEditorSegment, time: number, channel: VideoEditorChannel): boolean;
1214
+ /**
1215
+ * Calculate estimated total duration based on segments
1216
+ */
1217
+ declare function calculateEstimatedDuration(channels: VideoEditorChannel[]): number;
1218
+ /**
1219
+ * Calculate the timeline content end time (used for both ruler and scroll width)
1220
+ */
1221
+ declare function calculateTimelineContentEnd(channel: VideoEditorChannel): number;
1222
+ /**
1223
+ * Format time in mm:ss.ms
1224
+ */
1225
+ declare function formatTime(ms: number): string;
1226
+ /**
1227
+ * Parse time string to milliseconds
1228
+ */
1229
+ declare function parseTime(timeStr: string): number;
1230
+ /**
1231
+ * Generate a unique segment ID
1232
+ */
1233
+ declare function generateSegmentId(): string;
1234
+ /**
1235
+ * Generate a unique overlay ID
1236
+ */
1237
+ declare function generateOverlayId(): string;
1238
+
1168
1239
  /**
1169
1240
  * Hook exports for ugcinc-render
1170
1241
  *
@@ -1224,4 +1295,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
1224
1295
 
1225
1296
  declare const RenderRoot: React.FC;
1226
1297
 
1227
- export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateFitDimensions, calculateLineWidth, canSetAsReference, getBorderRadii, getDependentElements, getFontFamily, getReferenceElementX, getReferenceElementY, hexToRgba, isDynamicCropEnabled, parseHexColor, preloadFonts, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
1298
+ export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
package/dist/index.d.ts CHANGED
@@ -457,6 +457,15 @@ interface VideoEditorNodeConfig {
457
457
  previewUrls?: Record<string, string>;
458
458
  previewTextValues?: Record<string, string>;
459
459
  }
460
+ /**
461
+ * Segment position on timeline (calculated from segment timing properties)
462
+ */
463
+ interface SegmentTimelinePosition {
464
+ /** Start time in milliseconds */
465
+ startMs: number;
466
+ /** Duration in milliseconds */
467
+ durationMs: number;
468
+ }
460
469
 
461
470
  /**
462
471
  * Segment types for the rendering system
@@ -1165,6 +1174,68 @@ declare function calculateCropBounds(elements: ImageEditorElement[], dynamicCrop
1165
1174
  */
1166
1175
  declare function isDynamicCropEnabled(dynamicCrop: DynamicCropConfig | undefined): boolean;
1167
1176
 
1177
+ /**
1178
+ * Timeline utility functions for video editor
1179
+ *
1180
+ * These functions calculate segment positions on the timeline based on
1181
+ * the segment/overlay timing model. Used by both the webapp UI and
1182
+ * the VideoEditorComposition for rendering.
1183
+ */
1184
+
1185
+ /**
1186
+ * Create a default TimeValue for segment offsets
1187
+ */
1188
+ declare function defaultOffset(mode?: TimeMode): TimeValue;
1189
+ /**
1190
+ * Get base segments (no parentId) for a channel
1191
+ * Base segments are the primary timeline elements that overlays attach to
1192
+ */
1193
+ declare function getBaseSegments(channel: VideoEditorChannel): VideoEditorSegment[];
1194
+ /**
1195
+ * Get overlays for a specific parent segment (or global overlays if parentId is null)
1196
+ */
1197
+ declare function getOverlays(channel: VideoEditorChannel, parentId: string | null): VideoEditorSegment[];
1198
+ /**
1199
+ * Calculate segment position on timeline
1200
+ *
1201
+ * For base segments:
1202
+ * - Position is calculated from offset and previous segments
1203
+ * - Duration is from the duration property or default (5 seconds)
1204
+ *
1205
+ * For overlay segments:
1206
+ * - Position is calculated relative to parent using relativeStart/relativeEnd
1207
+ * - relativeStart/relativeEnd are fractions (0-1) of parent duration
1208
+ */
1209
+ declare function getSegmentTimelinePosition(segment: VideoEditorSegment, baseSegments: VideoEditorSegment[], channel: VideoEditorChannel): SegmentTimelinePosition;
1210
+ /**
1211
+ * Check if a segment is visible at a given time
1212
+ */
1213
+ declare function isSegmentVisibleAtTime(segment: VideoEditorSegment, time: number, channel: VideoEditorChannel): boolean;
1214
+ /**
1215
+ * Calculate estimated total duration based on segments
1216
+ */
1217
+ declare function calculateEstimatedDuration(channels: VideoEditorChannel[]): number;
1218
+ /**
1219
+ * Calculate the timeline content end time (used for both ruler and scroll width)
1220
+ */
1221
+ declare function calculateTimelineContentEnd(channel: VideoEditorChannel): number;
1222
+ /**
1223
+ * Format time in mm:ss.ms
1224
+ */
1225
+ declare function formatTime(ms: number): string;
1226
+ /**
1227
+ * Parse time string to milliseconds
1228
+ */
1229
+ declare function parseTime(timeStr: string): number;
1230
+ /**
1231
+ * Generate a unique segment ID
1232
+ */
1233
+ declare function generateSegmentId(): string;
1234
+ /**
1235
+ * Generate a unique overlay ID
1236
+ */
1237
+ declare function generateOverlayId(): string;
1238
+
1168
1239
  /**
1169
1240
  * Hook exports for ugcinc-render
1170
1241
  *
@@ -1224,4 +1295,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
1224
1295
 
1225
1296
  declare const RenderRoot: React.FC;
1226
1297
 
1227
- export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateFitDimensions, calculateLineWidth, canSetAsReference, getBorderRadii, getDependentElements, getFontFamily, getReferenceElementX, getReferenceElementY, hexToRgba, isDynamicCropEnabled, parseHexColor, preloadFonts, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
1298
+ export { type AudioSegment, type BaseEditorConfig, type BaseSegment, type BorderRadiusConfig, type Channel, type CropAxisConfig, type CropBoundary, type CropBounds, DIMENSION_PRESETS, type DimensionPreset, type DimensionPresetKey, type DynamicCropConfig, type EditorConfig, type EditorSegment, FONT_FAMILIES, FONT_URLS, type FitDimensions, type FitMode, type FontType, type FontWeight, type HorizontalAnchor, type HorizontalSelfAnchor, type Hyphenation, IMAGE_DEFAULTS, ImageEditorComposition, type ImageEditorCompositionProps, type ImageEditorConfig, type ImageEditorElement, type ImageEditorNodeConfig, ImageElement, type ImageElementProps, type ImageSegment, type PictureSegment, type PositionResolutionError, type PositionResolutionResult, type RelativePositionConfigX, type RelativePositionConfigY, RenderRoot, type Segment, type SegmentTimelinePosition, type SegmentType, type StaticSegment, TEXT_DEFAULTS, type TextAlignment, type TextDirection, TextElement, type TextElementProps, type TextOverflow, type TextSegment, type TextWrap, type TimeMode, type TimeValue, VIDEO_DEFAULTS, VISUAL_DEFAULTS, type VerticalAlignment, type VerticalAnchor, type VerticalSelfAnchor, type VideoEditorAudioSegment, type VideoEditorBaseSegment, type VideoEditorChannel, VideoEditorComposition, type VideoEditorCompositionProps, type VideoEditorConfig, type VideoEditorImageSegment, type VideoEditorNodeConfig, type VideoEditorSegment, type VideoEditorTextSegment, type VideoEditorVideoSegment, type VideoEditorVisualSegment, VideoElement, type VideoElementProps, type VideoSegment, type VisualSegment, type VisualSegmentUnion, type WordBreak, applyImageDefaults, applyTextDefaults, applyVideoDefaults, areFontsLoaded, buildFontString, calculateAutoWidthDimensions, calculateCropBounds, calculateEstimatedDuration, calculateFitDimensions, calculateLineWidth, calculateTimelineContentEnd, canSetAsReference, defaultOffset, formatTime, generateOverlayId, generateSegmentId, getBaseSegments, getBorderRadii, getDependentElements, getFontFamily, getOverlays, getReferenceElementX, getReferenceElementY, getSegmentTimelinePosition, hexToRgba, isDynamicCropEnabled, isSegmentVisibleAtTime, parseHexColor, parseTime, preloadFonts, resolveElementPositions, useFontsLoaded, useImageLoader, useImagePreloader, useResolvedPositions, wrapText };
package/dist/index.js CHANGED
@@ -40,17 +40,28 @@ __export(index_exports, {
40
40
  buildFontString: () => buildFontString,
41
41
  calculateAutoWidthDimensions: () => calculateAutoWidthDimensions,
42
42
  calculateCropBounds: () => calculateCropBounds,
43
+ calculateEstimatedDuration: () => calculateEstimatedDuration,
43
44
  calculateFitDimensions: () => calculateFitDimensions,
44
45
  calculateLineWidth: () => calculateLineWidth,
46
+ calculateTimelineContentEnd: () => calculateTimelineContentEnd,
45
47
  canSetAsReference: () => canSetAsReference,
48
+ defaultOffset: () => defaultOffset,
49
+ formatTime: () => formatTime,
50
+ generateOverlayId: () => generateOverlayId,
51
+ generateSegmentId: () => generateSegmentId,
52
+ getBaseSegments: () => getBaseSegments,
46
53
  getBorderRadii: () => getBorderRadii,
47
54
  getDependentElements: () => getDependentElements,
48
55
  getFontFamily: () => getFontFamily,
56
+ getOverlays: () => getOverlays,
49
57
  getReferenceElementX: () => getReferenceElementX,
50
58
  getReferenceElementY: () => getReferenceElementY,
59
+ getSegmentTimelinePosition: () => getSegmentTimelinePosition,
51
60
  hexToRgba: () => hexToRgba,
52
61
  isDynamicCropEnabled: () => isDynamicCropEnabled,
62
+ isSegmentVisibleAtTime: () => isSegmentVisibleAtTime,
53
63
  parseHexColor: () => parseHexColor,
64
+ parseTime: () => parseTime,
54
65
  preloadFonts: () => preloadFonts,
55
66
  resolveElementPositions: () => resolveElementPositions,
56
67
  useFontsLoaded: () => useFontsLoaded,
@@ -1461,6 +1472,101 @@ function calculateFitDimensions({
1461
1472
  };
1462
1473
  }
1463
1474
 
1475
+ // src/utils/timeline.ts
1476
+ function defaultOffset(mode = "flexible") {
1477
+ return mode === "flexible" ? { type: "relative", value: 0 } : { type: "absolute", value: 0 };
1478
+ }
1479
+ function getBaseSegments(channel) {
1480
+ return channel.segments.filter((s) => s.parentId === void 0);
1481
+ }
1482
+ function getOverlays(channel, parentId) {
1483
+ return channel.segments.filter((s) => s.parentId === (parentId ?? void 0));
1484
+ }
1485
+ function getSegmentTimelinePosition(segment, baseSegments, channel) {
1486
+ if (segment.parentId) {
1487
+ const parent = channel.segments.find((s) => s.id === segment.parentId);
1488
+ if (parent) {
1489
+ const parentPos = getSegmentTimelinePosition(parent, baseSegments, channel);
1490
+ const relStart = segment.relativeStart ?? 0;
1491
+ const relEnd = segment.relativeEnd ?? 1;
1492
+ return {
1493
+ startMs: parentPos.startMs + parentPos.durationMs * relStart,
1494
+ durationMs: parentPos.durationMs * (relEnd - relStart)
1495
+ };
1496
+ }
1497
+ }
1498
+ const baseIndex = baseSegments.findIndex((s) => s.id === segment.id);
1499
+ let accumulatedTime = 0;
1500
+ for (let i = 0; i < baseIndex; i++) {
1501
+ const prev = baseSegments[i];
1502
+ if (prev) {
1503
+ accumulatedTime += prev.duration?.type === "absolute" ? prev.duration.value : 5e3;
1504
+ }
1505
+ }
1506
+ const startMs = segment.offset.type === "absolute" ? segment.offset.value : accumulatedTime;
1507
+ const durationMs = segment.duration?.type === "absolute" ? segment.duration.value : 5e3;
1508
+ return { startMs, durationMs };
1509
+ }
1510
+ function isSegmentVisibleAtTime(segment, time, channel) {
1511
+ const baseSegments = getBaseSegments(channel);
1512
+ const { startMs, durationMs } = getSegmentTimelinePosition(segment, baseSegments, channel);
1513
+ const endMs = startMs + durationMs;
1514
+ return time >= startMs && time < endMs;
1515
+ }
1516
+ function calculateEstimatedDuration(channels) {
1517
+ let maxDuration = 5e3;
1518
+ for (const channel of channels) {
1519
+ let channelTime = 0;
1520
+ for (const segment of channel.segments) {
1521
+ if (segment.parentId) continue;
1522
+ if (segment.offset.type === "absolute") {
1523
+ channelTime = segment.offset.value;
1524
+ } else {
1525
+ channelTime += 5e3;
1526
+ }
1527
+ if (segment.duration?.type === "absolute") {
1528
+ channelTime += segment.duration.value;
1529
+ } else {
1530
+ channelTime += 5e3;
1531
+ }
1532
+ }
1533
+ maxDuration = Math.max(maxDuration, channelTime);
1534
+ }
1535
+ return maxDuration;
1536
+ }
1537
+ function calculateTimelineContentEnd(channel) {
1538
+ const baseSegments = getBaseSegments(channel);
1539
+ let lastEnd = 0;
1540
+ for (const segment of baseSegments) {
1541
+ const { startMs, durationMs } = getSegmentTimelinePosition(segment, baseSegments, channel);
1542
+ lastEnd = Math.max(lastEnd, startMs + durationMs);
1543
+ }
1544
+ return Math.ceil((lastEnd + 2e3) / 1e3) * 1e3;
1545
+ }
1546
+ function formatTime(ms) {
1547
+ const totalSeconds = Math.floor(ms / 1e3);
1548
+ const minutes = Math.floor(totalSeconds / 60);
1549
+ const seconds = totalSeconds % 60;
1550
+ const milliseconds = Math.floor(ms % 1e3 / 10);
1551
+ return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}.${milliseconds.toString().padStart(2, "0")}`;
1552
+ }
1553
+ function parseTime(timeStr) {
1554
+ const parts = timeStr.split(":");
1555
+ if (parts.length !== 2) return 0;
1556
+ const [minStr, secPart] = parts;
1557
+ const minutes = parseInt(minStr ?? "0", 10) || 0;
1558
+ const secParts = (secPart ?? "0").split(".");
1559
+ const seconds = parseInt(secParts[0] ?? "0", 10) || 0;
1560
+ const ms = parseInt((secParts[1] ?? "0").padEnd(2, "0").slice(0, 2), 10) * 10 || 0;
1561
+ return (minutes * 60 + seconds) * 1e3 + ms;
1562
+ }
1563
+ function generateSegmentId() {
1564
+ return `segment-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1565
+ }
1566
+ function generateOverlayId() {
1567
+ return `overlay-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1568
+ }
1569
+
1464
1570
  // src/hooks/index.ts
1465
1571
  var import_react6 = require("react");
1466
1572
  function useFontsLoaded() {
@@ -1618,17 +1724,28 @@ var RenderRoot = () => {
1618
1724
  buildFontString,
1619
1725
  calculateAutoWidthDimensions,
1620
1726
  calculateCropBounds,
1727
+ calculateEstimatedDuration,
1621
1728
  calculateFitDimensions,
1622
1729
  calculateLineWidth,
1730
+ calculateTimelineContentEnd,
1623
1731
  canSetAsReference,
1732
+ defaultOffset,
1733
+ formatTime,
1734
+ generateOverlayId,
1735
+ generateSegmentId,
1736
+ getBaseSegments,
1624
1737
  getBorderRadii,
1625
1738
  getDependentElements,
1626
1739
  getFontFamily,
1740
+ getOverlays,
1627
1741
  getReferenceElementX,
1628
1742
  getReferenceElementY,
1743
+ getSegmentTimelinePosition,
1629
1744
  hexToRgba,
1630
1745
  isDynamicCropEnabled,
1746
+ isSegmentVisibleAtTime,
1631
1747
  parseHexColor,
1748
+ parseTime,
1632
1749
  preloadFonts,
1633
1750
  resolveElementPositions,
1634
1751
  useFontsLoaded,
package/dist/index.mjs CHANGED
@@ -1398,6 +1398,101 @@ function calculateFitDimensions({
1398
1398
  };
1399
1399
  }
1400
1400
 
1401
+ // src/utils/timeline.ts
1402
+ function defaultOffset(mode = "flexible") {
1403
+ return mode === "flexible" ? { type: "relative", value: 0 } : { type: "absolute", value: 0 };
1404
+ }
1405
+ function getBaseSegments(channel) {
1406
+ return channel.segments.filter((s) => s.parentId === void 0);
1407
+ }
1408
+ function getOverlays(channel, parentId) {
1409
+ return channel.segments.filter((s) => s.parentId === (parentId ?? void 0));
1410
+ }
1411
+ function getSegmentTimelinePosition(segment, baseSegments, channel) {
1412
+ if (segment.parentId) {
1413
+ const parent = channel.segments.find((s) => s.id === segment.parentId);
1414
+ if (parent) {
1415
+ const parentPos = getSegmentTimelinePosition(parent, baseSegments, channel);
1416
+ const relStart = segment.relativeStart ?? 0;
1417
+ const relEnd = segment.relativeEnd ?? 1;
1418
+ return {
1419
+ startMs: parentPos.startMs + parentPos.durationMs * relStart,
1420
+ durationMs: parentPos.durationMs * (relEnd - relStart)
1421
+ };
1422
+ }
1423
+ }
1424
+ const baseIndex = baseSegments.findIndex((s) => s.id === segment.id);
1425
+ let accumulatedTime = 0;
1426
+ for (let i = 0; i < baseIndex; i++) {
1427
+ const prev = baseSegments[i];
1428
+ if (prev) {
1429
+ accumulatedTime += prev.duration?.type === "absolute" ? prev.duration.value : 5e3;
1430
+ }
1431
+ }
1432
+ const startMs = segment.offset.type === "absolute" ? segment.offset.value : accumulatedTime;
1433
+ const durationMs = segment.duration?.type === "absolute" ? segment.duration.value : 5e3;
1434
+ return { startMs, durationMs };
1435
+ }
1436
+ function isSegmentVisibleAtTime(segment, time, channel) {
1437
+ const baseSegments = getBaseSegments(channel);
1438
+ const { startMs, durationMs } = getSegmentTimelinePosition(segment, baseSegments, channel);
1439
+ const endMs = startMs + durationMs;
1440
+ return time >= startMs && time < endMs;
1441
+ }
1442
+ function calculateEstimatedDuration(channels) {
1443
+ let maxDuration = 5e3;
1444
+ for (const channel of channels) {
1445
+ let channelTime = 0;
1446
+ for (const segment of channel.segments) {
1447
+ if (segment.parentId) continue;
1448
+ if (segment.offset.type === "absolute") {
1449
+ channelTime = segment.offset.value;
1450
+ } else {
1451
+ channelTime += 5e3;
1452
+ }
1453
+ if (segment.duration?.type === "absolute") {
1454
+ channelTime += segment.duration.value;
1455
+ } else {
1456
+ channelTime += 5e3;
1457
+ }
1458
+ }
1459
+ maxDuration = Math.max(maxDuration, channelTime);
1460
+ }
1461
+ return maxDuration;
1462
+ }
1463
+ function calculateTimelineContentEnd(channel) {
1464
+ const baseSegments = getBaseSegments(channel);
1465
+ let lastEnd = 0;
1466
+ for (const segment of baseSegments) {
1467
+ const { startMs, durationMs } = getSegmentTimelinePosition(segment, baseSegments, channel);
1468
+ lastEnd = Math.max(lastEnd, startMs + durationMs);
1469
+ }
1470
+ return Math.ceil((lastEnd + 2e3) / 1e3) * 1e3;
1471
+ }
1472
+ function formatTime(ms) {
1473
+ const totalSeconds = Math.floor(ms / 1e3);
1474
+ const minutes = Math.floor(totalSeconds / 60);
1475
+ const seconds = totalSeconds % 60;
1476
+ const milliseconds = Math.floor(ms % 1e3 / 10);
1477
+ return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}.${milliseconds.toString().padStart(2, "0")}`;
1478
+ }
1479
+ function parseTime(timeStr) {
1480
+ const parts = timeStr.split(":");
1481
+ if (parts.length !== 2) return 0;
1482
+ const [minStr, secPart] = parts;
1483
+ const minutes = parseInt(minStr ?? "0", 10) || 0;
1484
+ const secParts = (secPart ?? "0").split(".");
1485
+ const seconds = parseInt(secParts[0] ?? "0", 10) || 0;
1486
+ const ms = parseInt((secParts[1] ?? "0").padEnd(2, "0").slice(0, 2), 10) * 10 || 0;
1487
+ return (minutes * 60 + seconds) * 1e3 + ms;
1488
+ }
1489
+ function generateSegmentId() {
1490
+ return `segment-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1491
+ }
1492
+ function generateOverlayId() {
1493
+ return `overlay-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1494
+ }
1495
+
1401
1496
  // src/hooks/index.ts
1402
1497
  import { useEffect, useState, useMemo as useMemo6 } from "react";
1403
1498
  function useFontsLoaded() {
@@ -1554,17 +1649,28 @@ export {
1554
1649
  buildFontString,
1555
1650
  calculateAutoWidthDimensions,
1556
1651
  calculateCropBounds,
1652
+ calculateEstimatedDuration,
1557
1653
  calculateFitDimensions,
1558
1654
  calculateLineWidth,
1655
+ calculateTimelineContentEnd,
1559
1656
  canSetAsReference,
1657
+ defaultOffset,
1658
+ formatTime,
1659
+ generateOverlayId,
1660
+ generateSegmentId,
1661
+ getBaseSegments,
1560
1662
  getBorderRadii,
1561
1663
  getDependentElements,
1562
1664
  getFontFamily,
1665
+ getOverlays,
1563
1666
  getReferenceElementX,
1564
1667
  getReferenceElementY,
1668
+ getSegmentTimelinePosition,
1565
1669
  hexToRgba,
1566
1670
  isDynamicCropEnabled,
1671
+ isSegmentVisibleAtTime,
1567
1672
  parseHexColor,
1673
+ parseTime,
1568
1674
  preloadFonts,
1569
1675
  resolveElementPositions,
1570
1676
  useFontsLoaded,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc-render",
3
- "version": "1.3.14",
3
+ "version": "1.4.0",
4
4
  "description": "Unified rendering package for UGC Inc - shared types, components, and compositions for pixel-perfect client/server rendering",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",