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 +127 -19
- package/dist/index.d.ts +127 -19
- package/dist/index.js +119 -3
- package/dist/index.mjs +108 -3
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -337,27 +337,47 @@ interface ImageEditorNodeConfig {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
|
-
* Video editor types for
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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 :
|
|
1248
|
+
durationMs = segment.duration.type === "absolute" ? segment.duration.value : 5e3;
|
|
1238
1249
|
} else {
|
|
1239
|
-
durationMs =
|
|
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 :
|
|
1174
|
+
durationMs = segment.duration.type === "absolute" ? segment.duration.value : 5e3;
|
|
1175
1175
|
} else {
|
|
1176
|
-
durationMs =
|
|
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
|
+
"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",
|