ugcinc-render 1.3.14 → 1.5.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
@@ -337,27 +337,47 @@ interface ImageEditorNodeConfig {
337
337
  }
338
338
 
339
339
  /**
340
- * Video editor types for rendering
340
+ * Video editor types for the WEBAPP UI
341
+ *
342
+ * These types include UI-only fields (marked with comments).
343
+ * The renderer uses types from editor.ts and segment.ts instead.
344
+ *
345
+ * UI-only fields are stripped before sending to renderer:
346
+ * - timeMode: UI helper for displaying timing mode
347
+ * - inputRef/textInputRef: Resolved to source/text before rendering
348
+ * - dimensionPreset: Renderer uses width/height directly
349
+ * - previewUrls/previewTextValues: Only for UI preview
341
350
  */
342
351
 
343
352
  /**
344
- * UI helper for timing mode
353
+ * UI helper for timing mode - not sent to renderer
345
354
  */
346
355
  type TimeMode = 'fixed' | 'flexible';
347
356
  /**
348
- * Base properties for all video editor segments
357
+ * Base properties for all video editor segments (UI)
349
358
  */
350
359
  interface VideoEditorBaseSegment {
360
+ /** Unique segment identifier */
351
361
  id: string;
362
+ /** Order in the channel (0-based) */
352
363
  order: number;
364
+ /** Time offset from previous segment or absolute position */
353
365
  offset: TimeValue;
366
+ /** Segment duration - required for sequential playback */
354
367
  duration?: TimeValue;
368
+ /** Trim from start in milliseconds */
355
369
  startTrim?: number;
370
+ /** Trim from end in milliseconds */
356
371
  endTrim?: number;
372
+ /** UI-only: helper for timing mode display - stripped before rendering */
357
373
  timeMode?: TimeMode;
374
+ /** Parent segment ID for overlays */
358
375
  parentId?: string;
376
+ /** Relative start (0-1) within parent for overlays */
359
377
  relativeStart?: number;
378
+ /** Relative end (0-1) within parent for overlays */
360
379
  relativeEnd?: number;
380
+ /** Fade-in duration in milliseconds */
361
381
  fadeIn?: number;
362
382
  }
363
383
  /**
@@ -374,11 +394,13 @@ interface VideoEditorVisualSegment extends VideoEditorBaseSegment {
374
394
  opacity?: number;
375
395
  }
376
396
  /**
377
- * Video segment
397
+ * Video segment (UI)
378
398
  */
379
399
  interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
380
400
  type: 'video';
401
+ /** UI-only: reference to input node - resolved to source before rendering */
381
402
  inputRef?: string;
403
+ /** Actual source URL (set after inputRef is resolved) */
382
404
  source?: string;
383
405
  fit?: FitMode;
384
406
  speed?: number;
@@ -386,31 +408,37 @@ interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
386
408
  borderRadius?: number | BorderRadiusConfig;
387
409
  }
388
410
  /**
389
- * Audio segment
411
+ * Audio segment (UI)
390
412
  */
391
413
  interface VideoEditorAudioSegment extends VideoEditorBaseSegment {
392
414
  type: 'audio';
415
+ /** UI-only: reference to input node - resolved to source before rendering */
393
416
  inputRef?: string;
417
+ /** Actual source URL (set after inputRef is resolved) */
394
418
  source?: string;
395
419
  volume?: number;
396
420
  }
397
421
  /**
398
- * Image segment
422
+ * Image segment (UI)
399
423
  */
400
424
  interface VideoEditorImageSegment extends VideoEditorVisualSegment {
401
425
  type: 'image';
426
+ /** UI-only: reference to input node - resolved to source before rendering */
402
427
  inputRef?: string;
428
+ /** Actual source URL (set after inputRef is resolved) */
403
429
  source?: string;
404
430
  fit?: FitMode;
405
431
  loop?: boolean;
406
432
  borderRadius?: number | BorderRadiusConfig;
407
433
  }
408
434
  /**
409
- * Text segment
435
+ * Text segment (UI)
410
436
  */
411
437
  interface VideoEditorTextSegment extends VideoEditorVisualSegment {
412
438
  type: 'text';
439
+ /** UI-only: reference to text input - resolved to text before rendering */
413
440
  textInputRef?: string;
441
+ /** Actual text content (set after textInputRef is resolved) */
414
442
  text?: string;
415
443
  alignment?: 'left' | 'center' | 'right' | 'justify';
416
444
  verticalAlign?: 'top' | 'middle' | 'bottom';
@@ -445,18 +473,36 @@ interface VideoEditorChannel {
445
473
  segments: VideoEditorSegment[];
446
474
  }
447
475
  /**
448
- * Video editor node configuration
476
+ * Video editor node configuration for WEBAPP UI
477
+ *
478
+ * Duration is NOT stored - it's calculated from segment durations.
479
+ * dimensionPreset, previewUrls, previewTextValues are UI-only.
449
480
  */
450
481
  interface VideoEditorNodeConfig {
482
+ /** Canvas width in pixels */
451
483
  width: number;
484
+ /** Canvas height in pixels */
452
485
  height: number;
486
+ /** Frames per second */
453
487
  fps: number;
454
- duration?: number;
488
+ /** UI-only: dimension preset selector */
455
489
  dimensionPreset: DimensionPresetKey;
490
+ /** Channels containing segments */
456
491
  channels: VideoEditorChannel[];
492
+ /** UI-only: preview URLs for displaying in editor */
457
493
  previewUrls?: Record<string, string>;
494
+ /** UI-only: preview text values for displaying in editor */
458
495
  previewTextValues?: Record<string, string>;
459
496
  }
497
+ /**
498
+ * Segment position on timeline (calculated from segment timing properties)
499
+ */
500
+ interface SegmentTimelinePosition {
501
+ /** Start time in milliseconds */
502
+ startMs: number;
503
+ /** Duration in milliseconds */
504
+ durationMs: number;
505
+ }
460
506
 
461
507
  /**
462
508
  * Segment types for the rendering system
@@ -635,15 +681,16 @@ type VisualSegmentUnion = VideoSegment | ImageSegment | TextSegment;
635
681
  type StaticSegment = ImageSegment | TextSegment;
636
682
 
637
683
  /**
638
- * Editor configuration types
684
+ * Editor configuration types for RENDERING
685
+ *
686
+ * These types define what the renderer receives.
687
+ * Duration is always calculated from segments - there is no duration field.
639
688
  *
640
- * Editors are the top-level containers that define the canvas dimensions,
641
- * timing, and channels containing segments.
689
+ * Note: UI-specific types (with previewUrls, dimensionPreset, etc.) are in video.ts
642
690
  */
643
691
 
644
692
  /**
645
- * A channel (track) containing segments
646
- * Channels allow for parallel playback of segments
693
+ * A channel (track) containing segments for rendering
647
694
  */
648
695
  interface Channel<T extends Segment = Segment> {
649
696
  /** Unique channel identifier */
@@ -652,17 +699,16 @@ interface Channel<T extends Segment = Segment> {
652
699
  segments: T[];
653
700
  }
654
701
  /**
655
- * Base editor configuration shared by all editor types
702
+ * Base editor configuration for rendering
703
+ * Duration is calculated from segments, not stored.
656
704
  */
657
705
  interface BaseEditorConfig {
658
706
  /** Canvas width in pixels */
659
707
  width: number;
660
708
  /** Canvas height in pixels */
661
709
  height: number;
662
- /** Frames per second (video only) */
710
+ /** Frames per second */
663
711
  fps: number;
664
- /** Total duration in milliseconds (auto-calculated if 0 or undefined) */
665
- duration?: number;
666
712
  }
667
713
  /**
668
714
  * Full editor configuration with all segment types
@@ -1165,6 +1211,68 @@ declare function calculateCropBounds(elements: ImageEditorElement[], dynamicCrop
1165
1211
  */
1166
1212
  declare function isDynamicCropEnabled(dynamicCrop: DynamicCropConfig | undefined): boolean;
1167
1213
 
1214
+ /**
1215
+ * Timeline utility functions for video editor
1216
+ *
1217
+ * These functions calculate segment positions on the timeline based on
1218
+ * the segment/overlay timing model. Used by both the webapp UI and
1219
+ * the VideoEditorComposition for rendering.
1220
+ */
1221
+
1222
+ /**
1223
+ * Create a default TimeValue for segment offsets
1224
+ */
1225
+ declare function defaultOffset(mode?: TimeMode): TimeValue;
1226
+ /**
1227
+ * Get base segments (no parentId) for a channel
1228
+ * Base segments are the primary timeline elements that overlays attach to
1229
+ */
1230
+ declare function getBaseSegments(channel: VideoEditorChannel): VideoEditorSegment[];
1231
+ /**
1232
+ * Get overlays for a specific parent segment (or global overlays if parentId is null)
1233
+ */
1234
+ declare function getOverlays(channel: VideoEditorChannel, parentId: string | null): VideoEditorSegment[];
1235
+ /**
1236
+ * Calculate segment position on timeline
1237
+ *
1238
+ * For base segments:
1239
+ * - Position is calculated from offset and previous segments
1240
+ * - Duration is from the duration property or default (5 seconds)
1241
+ *
1242
+ * For overlay segments:
1243
+ * - Position is calculated relative to parent using relativeStart/relativeEnd
1244
+ * - relativeStart/relativeEnd are fractions (0-1) of parent duration
1245
+ */
1246
+ declare function getSegmentTimelinePosition(segment: VideoEditorSegment, baseSegments: VideoEditorSegment[], channel: VideoEditorChannel): SegmentTimelinePosition;
1247
+ /**
1248
+ * Check if a segment is visible at a given time
1249
+ */
1250
+ declare function isSegmentVisibleAtTime(segment: VideoEditorSegment, time: number, channel: VideoEditorChannel): boolean;
1251
+ /**
1252
+ * Calculate estimated total duration based on segments
1253
+ */
1254
+ declare function calculateEstimatedDuration(channels: VideoEditorChannel[]): number;
1255
+ /**
1256
+ * Calculate the timeline content end time (used for both ruler and scroll width)
1257
+ */
1258
+ declare function calculateTimelineContentEnd(channel: VideoEditorChannel): number;
1259
+ /**
1260
+ * Format time in mm:ss.ms
1261
+ */
1262
+ declare function formatTime(ms: number): string;
1263
+ /**
1264
+ * Parse time string to milliseconds
1265
+ */
1266
+ declare function parseTime(timeStr: string): number;
1267
+ /**
1268
+ * Generate a unique segment ID
1269
+ */
1270
+ declare function generateSegmentId(): string;
1271
+ /**
1272
+ * Generate a unique overlay ID
1273
+ */
1274
+ declare function generateOverlayId(): string;
1275
+
1168
1276
  /**
1169
1277
  * Hook exports for ugcinc-render
1170
1278
  *
@@ -1224,4 +1332,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
1224
1332
 
1225
1333
  declare const RenderRoot: React.FC;
1226
1334
 
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 };
1335
+ 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
@@ -337,27 +337,47 @@ interface ImageEditorNodeConfig {
337
337
  }
338
338
 
339
339
  /**
340
- * Video editor types for rendering
340
+ * Video editor types for the WEBAPP UI
341
+ *
342
+ * These types include UI-only fields (marked with comments).
343
+ * The renderer uses types from editor.ts and segment.ts instead.
344
+ *
345
+ * UI-only fields are stripped before sending to renderer:
346
+ * - timeMode: UI helper for displaying timing mode
347
+ * - inputRef/textInputRef: Resolved to source/text before rendering
348
+ * - dimensionPreset: Renderer uses width/height directly
349
+ * - previewUrls/previewTextValues: Only for UI preview
341
350
  */
342
351
 
343
352
  /**
344
- * UI helper for timing mode
353
+ * UI helper for timing mode - not sent to renderer
345
354
  */
346
355
  type TimeMode = 'fixed' | 'flexible';
347
356
  /**
348
- * Base properties for all video editor segments
357
+ * Base properties for all video editor segments (UI)
349
358
  */
350
359
  interface VideoEditorBaseSegment {
360
+ /** Unique segment identifier */
351
361
  id: string;
362
+ /** Order in the channel (0-based) */
352
363
  order: number;
364
+ /** Time offset from previous segment or absolute position */
353
365
  offset: TimeValue;
366
+ /** Segment duration - required for sequential playback */
354
367
  duration?: TimeValue;
368
+ /** Trim from start in milliseconds */
355
369
  startTrim?: number;
370
+ /** Trim from end in milliseconds */
356
371
  endTrim?: number;
372
+ /** UI-only: helper for timing mode display - stripped before rendering */
357
373
  timeMode?: TimeMode;
374
+ /** Parent segment ID for overlays */
358
375
  parentId?: string;
376
+ /** Relative start (0-1) within parent for overlays */
359
377
  relativeStart?: number;
378
+ /** Relative end (0-1) within parent for overlays */
360
379
  relativeEnd?: number;
380
+ /** Fade-in duration in milliseconds */
361
381
  fadeIn?: number;
362
382
  }
363
383
  /**
@@ -374,11 +394,13 @@ interface VideoEditorVisualSegment extends VideoEditorBaseSegment {
374
394
  opacity?: number;
375
395
  }
376
396
  /**
377
- * Video segment
397
+ * Video segment (UI)
378
398
  */
379
399
  interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
380
400
  type: 'video';
401
+ /** UI-only: reference to input node - resolved to source before rendering */
381
402
  inputRef?: string;
403
+ /** Actual source URL (set after inputRef is resolved) */
382
404
  source?: string;
383
405
  fit?: FitMode;
384
406
  speed?: number;
@@ -386,31 +408,37 @@ interface VideoEditorVideoSegment extends VideoEditorVisualSegment {
386
408
  borderRadius?: number | BorderRadiusConfig;
387
409
  }
388
410
  /**
389
- * Audio segment
411
+ * Audio segment (UI)
390
412
  */
391
413
  interface VideoEditorAudioSegment extends VideoEditorBaseSegment {
392
414
  type: 'audio';
415
+ /** UI-only: reference to input node - resolved to source before rendering */
393
416
  inputRef?: string;
417
+ /** Actual source URL (set after inputRef is resolved) */
394
418
  source?: string;
395
419
  volume?: number;
396
420
  }
397
421
  /**
398
- * Image segment
422
+ * Image segment (UI)
399
423
  */
400
424
  interface VideoEditorImageSegment extends VideoEditorVisualSegment {
401
425
  type: 'image';
426
+ /** UI-only: reference to input node - resolved to source before rendering */
402
427
  inputRef?: string;
428
+ /** Actual source URL (set after inputRef is resolved) */
403
429
  source?: string;
404
430
  fit?: FitMode;
405
431
  loop?: boolean;
406
432
  borderRadius?: number | BorderRadiusConfig;
407
433
  }
408
434
  /**
409
- * Text segment
435
+ * Text segment (UI)
410
436
  */
411
437
  interface VideoEditorTextSegment extends VideoEditorVisualSegment {
412
438
  type: 'text';
439
+ /** UI-only: reference to text input - resolved to text before rendering */
413
440
  textInputRef?: string;
441
+ /** Actual text content (set after textInputRef is resolved) */
414
442
  text?: string;
415
443
  alignment?: 'left' | 'center' | 'right' | 'justify';
416
444
  verticalAlign?: 'top' | 'middle' | 'bottom';
@@ -445,18 +473,36 @@ interface VideoEditorChannel {
445
473
  segments: VideoEditorSegment[];
446
474
  }
447
475
  /**
448
- * Video editor node configuration
476
+ * Video editor node configuration for WEBAPP UI
477
+ *
478
+ * Duration is NOT stored - it's calculated from segment durations.
479
+ * dimensionPreset, previewUrls, previewTextValues are UI-only.
449
480
  */
450
481
  interface VideoEditorNodeConfig {
482
+ /** Canvas width in pixels */
451
483
  width: number;
484
+ /** Canvas height in pixels */
452
485
  height: number;
486
+ /** Frames per second */
453
487
  fps: number;
454
- duration?: number;
488
+ /** UI-only: dimension preset selector */
455
489
  dimensionPreset: DimensionPresetKey;
490
+ /** Channels containing segments */
456
491
  channels: VideoEditorChannel[];
492
+ /** UI-only: preview URLs for displaying in editor */
457
493
  previewUrls?: Record<string, string>;
494
+ /** UI-only: preview text values for displaying in editor */
458
495
  previewTextValues?: Record<string, string>;
459
496
  }
497
+ /**
498
+ * Segment position on timeline (calculated from segment timing properties)
499
+ */
500
+ interface SegmentTimelinePosition {
501
+ /** Start time in milliseconds */
502
+ startMs: number;
503
+ /** Duration in milliseconds */
504
+ durationMs: number;
505
+ }
460
506
 
461
507
  /**
462
508
  * Segment types for the rendering system
@@ -635,15 +681,16 @@ type VisualSegmentUnion = VideoSegment | ImageSegment | TextSegment;
635
681
  type StaticSegment = ImageSegment | TextSegment;
636
682
 
637
683
  /**
638
- * Editor configuration types
684
+ * Editor configuration types for RENDERING
685
+ *
686
+ * These types define what the renderer receives.
687
+ * Duration is always calculated from segments - there is no duration field.
639
688
  *
640
- * Editors are the top-level containers that define the canvas dimensions,
641
- * timing, and channels containing segments.
689
+ * Note: UI-specific types (with previewUrls, dimensionPreset, etc.) are in video.ts
642
690
  */
643
691
 
644
692
  /**
645
- * A channel (track) containing segments
646
- * Channels allow for parallel playback of segments
693
+ * A channel (track) containing segments for rendering
647
694
  */
648
695
  interface Channel<T extends Segment = Segment> {
649
696
  /** Unique channel identifier */
@@ -652,17 +699,16 @@ interface Channel<T extends Segment = Segment> {
652
699
  segments: T[];
653
700
  }
654
701
  /**
655
- * Base editor configuration shared by all editor types
702
+ * Base editor configuration for rendering
703
+ * Duration is calculated from segments, not stored.
656
704
  */
657
705
  interface BaseEditorConfig {
658
706
  /** Canvas width in pixels */
659
707
  width: number;
660
708
  /** Canvas height in pixels */
661
709
  height: number;
662
- /** Frames per second (video only) */
710
+ /** Frames per second */
663
711
  fps: number;
664
- /** Total duration in milliseconds (auto-calculated if 0 or undefined) */
665
- duration?: number;
666
712
  }
667
713
  /**
668
714
  * Full editor configuration with all segment types
@@ -1165,6 +1211,68 @@ declare function calculateCropBounds(elements: ImageEditorElement[], dynamicCrop
1165
1211
  */
1166
1212
  declare function isDynamicCropEnabled(dynamicCrop: DynamicCropConfig | undefined): boolean;
1167
1213
 
1214
+ /**
1215
+ * Timeline utility functions for video editor
1216
+ *
1217
+ * These functions calculate segment positions on the timeline based on
1218
+ * the segment/overlay timing model. Used by both the webapp UI and
1219
+ * the VideoEditorComposition for rendering.
1220
+ */
1221
+
1222
+ /**
1223
+ * Create a default TimeValue for segment offsets
1224
+ */
1225
+ declare function defaultOffset(mode?: TimeMode): TimeValue;
1226
+ /**
1227
+ * Get base segments (no parentId) for a channel
1228
+ * Base segments are the primary timeline elements that overlays attach to
1229
+ */
1230
+ declare function getBaseSegments(channel: VideoEditorChannel): VideoEditorSegment[];
1231
+ /**
1232
+ * Get overlays for a specific parent segment (or global overlays if parentId is null)
1233
+ */
1234
+ declare function getOverlays(channel: VideoEditorChannel, parentId: string | null): VideoEditorSegment[];
1235
+ /**
1236
+ * Calculate segment position on timeline
1237
+ *
1238
+ * For base segments:
1239
+ * - Position is calculated from offset and previous segments
1240
+ * - Duration is from the duration property or default (5 seconds)
1241
+ *
1242
+ * For overlay segments:
1243
+ * - Position is calculated relative to parent using relativeStart/relativeEnd
1244
+ * - relativeStart/relativeEnd are fractions (0-1) of parent duration
1245
+ */
1246
+ declare function getSegmentTimelinePosition(segment: VideoEditorSegment, baseSegments: VideoEditorSegment[], channel: VideoEditorChannel): SegmentTimelinePosition;
1247
+ /**
1248
+ * Check if a segment is visible at a given time
1249
+ */
1250
+ declare function isSegmentVisibleAtTime(segment: VideoEditorSegment, time: number, channel: VideoEditorChannel): boolean;
1251
+ /**
1252
+ * Calculate estimated total duration based on segments
1253
+ */
1254
+ declare function calculateEstimatedDuration(channels: VideoEditorChannel[]): number;
1255
+ /**
1256
+ * Calculate the timeline content end time (used for both ruler and scroll width)
1257
+ */
1258
+ declare function calculateTimelineContentEnd(channel: VideoEditorChannel): number;
1259
+ /**
1260
+ * Format time in mm:ss.ms
1261
+ */
1262
+ declare function formatTime(ms: number): string;
1263
+ /**
1264
+ * Parse time string to milliseconds
1265
+ */
1266
+ declare function parseTime(timeStr: string): number;
1267
+ /**
1268
+ * Generate a unique segment ID
1269
+ */
1270
+ declare function generateSegmentId(): string;
1271
+ /**
1272
+ * Generate a unique overlay ID
1273
+ */
1274
+ declare function generateOverlayId(): string;
1275
+
1168
1276
  /**
1169
1277
  * Hook exports for ugcinc-render
1170
1278
  *
@@ -1224,4 +1332,4 @@ declare function useResolvedPositions(elements: ImageEditorElement[], textValues
1224
1332
 
1225
1333
  declare const RenderRoot: React.FC;
1226
1334
 
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 };
1335
+ 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,
@@ -1234,9 +1245,9 @@ function calculateSegmentTimings(config, fps) {
1234
1245
  const startFrame = currentFrame + offsetFrames;
1235
1246
  let durationMs;
1236
1247
  if (segment.duration) {
1237
- durationMs = segment.duration.type === "absolute" ? segment.duration.value : segment.duration.value * (config.duration ?? 1e4);
1248
+ durationMs = segment.duration.type === "absolute" ? segment.duration.value : 5e3;
1238
1249
  } else {
1239
- durationMs = (config.duration ?? 1e4) - startFrame / fps * 1e3;
1250
+ durationMs = 5e3;
1240
1251
  }
1241
1252
  const durationInFrames = Math.max(1, Math.round(durationMs / 1e3 * fps));
1242
1253
  const endFrame = startFrame + durationInFrames;
@@ -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() {
@@ -1560,7 +1666,6 @@ var defaultVideoProps = {
1560
1666
  width: 1080,
1561
1667
  height: 1920,
1562
1668
  fps: 30,
1563
- duration: 5e3,
1564
1669
  channels: []
1565
1670
  },
1566
1671
  sources: {},
@@ -1618,17 +1723,28 @@ var RenderRoot = () => {
1618
1723
  buildFontString,
1619
1724
  calculateAutoWidthDimensions,
1620
1725
  calculateCropBounds,
1726
+ calculateEstimatedDuration,
1621
1727
  calculateFitDimensions,
1622
1728
  calculateLineWidth,
1729
+ calculateTimelineContentEnd,
1623
1730
  canSetAsReference,
1731
+ defaultOffset,
1732
+ formatTime,
1733
+ generateOverlayId,
1734
+ generateSegmentId,
1735
+ getBaseSegments,
1624
1736
  getBorderRadii,
1625
1737
  getDependentElements,
1626
1738
  getFontFamily,
1739
+ getOverlays,
1627
1740
  getReferenceElementX,
1628
1741
  getReferenceElementY,
1742
+ getSegmentTimelinePosition,
1629
1743
  hexToRgba,
1630
1744
  isDynamicCropEnabled,
1745
+ isSegmentVisibleAtTime,
1631
1746
  parseHexColor,
1747
+ parseTime,
1632
1748
  preloadFonts,
1633
1749
  resolveElementPositions,
1634
1750
  useFontsLoaded,
package/dist/index.mjs CHANGED
@@ -1171,9 +1171,9 @@ function calculateSegmentTimings(config, fps) {
1171
1171
  const startFrame = currentFrame + offsetFrames;
1172
1172
  let durationMs;
1173
1173
  if (segment.duration) {
1174
- durationMs = segment.duration.type === "absolute" ? segment.duration.value : segment.duration.value * (config.duration ?? 1e4);
1174
+ durationMs = segment.duration.type === "absolute" ? segment.duration.value : 5e3;
1175
1175
  } else {
1176
- durationMs = (config.duration ?? 1e4) - startFrame / fps * 1e3;
1176
+ durationMs = 5e3;
1177
1177
  }
1178
1178
  const durationInFrames = Math.max(1, Math.round(durationMs / 1e3 * fps));
1179
1179
  const endFrame = startFrame + durationInFrames;
@@ -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() {
@@ -1497,7 +1592,6 @@ var defaultVideoProps = {
1497
1592
  width: 1080,
1498
1593
  height: 1920,
1499
1594
  fps: 30,
1500
- duration: 5e3,
1501
1595
  channels: []
1502
1596
  },
1503
1597
  sources: {},
@@ -1554,17 +1648,28 @@ export {
1554
1648
  buildFontString,
1555
1649
  calculateAutoWidthDimensions,
1556
1650
  calculateCropBounds,
1651
+ calculateEstimatedDuration,
1557
1652
  calculateFitDimensions,
1558
1653
  calculateLineWidth,
1654
+ calculateTimelineContentEnd,
1559
1655
  canSetAsReference,
1656
+ defaultOffset,
1657
+ formatTime,
1658
+ generateOverlayId,
1659
+ generateSegmentId,
1660
+ getBaseSegments,
1560
1661
  getBorderRadii,
1561
1662
  getDependentElements,
1562
1663
  getFontFamily,
1664
+ getOverlays,
1563
1665
  getReferenceElementX,
1564
1666
  getReferenceElementY,
1667
+ getSegmentTimelinePosition,
1565
1668
  hexToRgba,
1566
1669
  isDynamicCropEnabled,
1670
+ isSegmentVisibleAtTime,
1567
1671
  parseHexColor,
1672
+ parseTime,
1568
1673
  preloadFonts,
1569
1674
  resolveElementPositions,
1570
1675
  useFontsLoaded,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc-render",
3
- "version": "1.3.14",
3
+ "version": "1.5.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",