ugcinc 3.84.6 → 3.85.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.
Files changed (120) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +6 -1
  3. package/dist/render/Root.d.ts +12 -0
  4. package/dist/render/Root.js +508 -0
  5. package/dist/render/components/CaptionOverlay.d.ts +21 -0
  6. package/dist/render/components/CaptionOverlay.js +210 -0
  7. package/dist/render/components/ImageElement.d.ts +26 -0
  8. package/dist/render/components/ImageElement.js +88 -0
  9. package/dist/render/components/TextElement.d.ts +30 -0
  10. package/dist/render/components/TextElement.js +390 -0
  11. package/dist/render/components/VideoElement.d.ts +30 -0
  12. package/dist/render/components/VideoElement.js +108 -0
  13. package/dist/render/components/index.d.ts +7 -0
  14. package/dist/render/components/index.js +14 -0
  15. package/dist/render/compositions/AutoCaptionComposition.d.ts +42 -0
  16. package/dist/render/compositions/AutoCaptionComposition.js +29 -0
  17. package/dist/render/compositions/DmComposition/BaseDmComposition.d.ts +112 -0
  18. package/dist/render/compositions/DmComposition/BaseDmComposition.js +212 -0
  19. package/dist/render/compositions/DmComposition/DebugOverlay.d.ts +20 -0
  20. package/dist/render/compositions/DmComposition/DebugOverlay.js +258 -0
  21. package/dist/render/compositions/DmComposition/index.d.ts +6 -0
  22. package/dist/render/compositions/DmComposition/index.js +10 -0
  23. package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.d.ts +27 -0
  24. package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.js +629 -0
  25. package/dist/render/compositions/IMessageDmComposition/index.d.ts +20 -0
  26. package/dist/render/compositions/IMessageDmComposition/index.js +485 -0
  27. package/dist/render/compositions/IMessageDmComposition/types.d.ts +756 -0
  28. package/dist/render/compositions/IMessageDmComposition/types.js +225 -0
  29. package/dist/render/compositions/ImageEditorComposition.d.ts +74 -0
  30. package/dist/render/compositions/ImageEditorComposition.js +351 -0
  31. package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.d.ts +60 -0
  32. package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.js +1318 -0
  33. package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.d.ts +30 -0
  34. package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.js +131 -0
  35. package/dist/render/compositions/InstagramDmComposition/index.d.ts +16 -0
  36. package/dist/render/compositions/InstagramDmComposition/index.js +374 -0
  37. package/dist/render/compositions/InstagramDmComposition/theme.d.ts +42 -0
  38. package/dist/render/compositions/InstagramDmComposition/theme.js +55 -0
  39. package/dist/render/compositions/InstagramDmComposition/types.d.ts +215 -0
  40. package/dist/render/compositions/InstagramDmComposition/types.js +7 -0
  41. package/dist/render/compositions/ScreenshotAnimation.d.ts +14 -0
  42. package/dist/render/compositions/ScreenshotAnimation.js +268 -0
  43. package/dist/render/compositions/VideoEditorComposition.d.ts +45 -0
  44. package/dist/render/compositions/VideoEditorComposition.js +307 -0
  45. package/dist/render/compositions/index.d.ts +12 -0
  46. package/dist/render/compositions/index.js +43 -0
  47. package/dist/render/compositions/messaging/components/MediaBubble.d.ts +22 -0
  48. package/dist/render/compositions/messaging/components/MediaBubble.js +25 -0
  49. package/dist/render/compositions/messaging/components/MessageBubble.d.ts +35 -0
  50. package/dist/render/compositions/messaging/components/MessageBubble.js +34 -0
  51. package/dist/render/compositions/messaging/components/ProfilePic.d.ts +23 -0
  52. package/dist/render/compositions/messaging/components/ProfilePic.js +37 -0
  53. package/dist/render/compositions/messaging/components/Reaction.d.ts +23 -0
  54. package/dist/render/compositions/messaging/components/Reaction.js +19 -0
  55. package/dist/render/compositions/messaging/components/TypingIndicator.d.ts +25 -0
  56. package/dist/render/compositions/messaging/components/TypingIndicator.js +66 -0
  57. package/dist/render/compositions/messaging/index.d.ts +14 -0
  58. package/dist/render/compositions/messaging/index.js +43 -0
  59. package/dist/render/compositions/messaging/types.d.ts +148 -0
  60. package/dist/render/compositions/messaging/types.js +21 -0
  61. package/dist/render/compositions/messaging/utils/bubbleRadius.d.ts +45 -0
  62. package/dist/render/compositions/messaging/utils/bubbleRadius.js +84 -0
  63. package/dist/render/compositions/messaging/utils/groupMessages.d.ts +41 -0
  64. package/dist/render/compositions/messaging/utils/groupMessages.js +110 -0
  65. package/dist/render/data/phone-top-nav.d.ts +1 -0
  66. package/dist/render/data/phone-top-nav.js +4 -0
  67. package/dist/render/data/screenshot.d.ts +164 -0
  68. package/dist/render/data/screenshot.js +63 -0
  69. package/dist/render/hooks/index.d.ts +54 -0
  70. package/dist/render/hooks/index.js +132 -0
  71. package/dist/render/index.d.ts +12 -0
  72. package/dist/render/index.js +36 -0
  73. package/dist/render/types/base.d.ts +148 -0
  74. package/dist/render/types/base.js +5 -0
  75. package/dist/render/types/caption.d.ts +105 -0
  76. package/dist/render/types/caption.js +8 -0
  77. package/dist/render/types/crop.d.ts +60 -0
  78. package/dist/render/types/crop.js +8 -0
  79. package/dist/render/types/deduplication.d.ts +284 -0
  80. package/dist/render/types/deduplication.js +240 -0
  81. package/dist/render/types/editor.d.ts +97 -0
  82. package/dist/render/types/editor.js +10 -0
  83. package/dist/render/types/element.d.ts +139 -0
  84. package/dist/render/types/element.js +19 -0
  85. package/dist/render/types/index.d.ts +20 -0
  86. package/dist/render/types/index.js +24 -0
  87. package/dist/render/types/instagram-dm-public.d.ts +60 -0
  88. package/dist/render/types/instagram-dm-public.js +8 -0
  89. package/dist/render/types/position.d.ts +59 -0
  90. package/dist/render/types/position.js +5 -0
  91. package/dist/render/types/screenshot.d.ts +57 -0
  92. package/dist/render/types/screenshot.js +34 -0
  93. package/dist/render/types/segment.d.ts +163 -0
  94. package/dist/render/types/segment.js +8 -0
  95. package/dist/render/types/video.d.ts +192 -0
  96. package/dist/render/types/video.js +14 -0
  97. package/dist/render/utils/captionPresets.d.ts +38 -0
  98. package/dist/render/utils/captionPresets.js +168 -0
  99. package/dist/render/utils/cropBounds.d.ts +20 -0
  100. package/dist/render/utils/cropBounds.js +166 -0
  101. package/dist/render/utils/defaults.d.ts +74 -0
  102. package/dist/render/utils/defaults.js +91 -0
  103. package/dist/render/utils/emoji.d.ts +40 -0
  104. package/dist/render/utils/emoji.js +105 -0
  105. package/dist/render/utils/fit.d.ts +35 -0
  106. package/dist/render/utils/fit.js +63 -0
  107. package/dist/render/utils/fonts.d.ts +55 -0
  108. package/dist/render/utils/fonts.js +191 -0
  109. package/dist/render/utils/index.d.ts +14 -0
  110. package/dist/render/utils/index.js +73 -0
  111. package/dist/render/utils/positionResolver.d.ts +64 -0
  112. package/dist/render/utils/positionResolver.js +508 -0
  113. package/dist/render/utils/text.d.ts +50 -0
  114. package/dist/render/utils/text.js +177 -0
  115. package/dist/render/utils/timeline.d.ts +62 -0
  116. package/dist/render/utils/timeline.js +172 -0
  117. package/dist/render.d.ts +1 -1
  118. package/dist/types.d.ts +136 -17
  119. package/dist/types.js +58 -0
  120. package/package.json +20 -6
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ /**
3
+ * Text utilities for measuring and wrapping text
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.wrapText = wrapText;
7
+ exports.calculateLineWidth = calculateLineWidth;
8
+ exports.getBorderRadii = getBorderRadii;
9
+ exports.parseHexColor = parseHexColor;
10
+ exports.hexToRgba = hexToRgba;
11
+ exports.getSFProLetterSpacing = getSFProLetterSpacing;
12
+ /**
13
+ * Wrap text to fit within a specific width
14
+ * Accounts for letter spacing when measuring text width
15
+ */
16
+ function wrapText({ text, maxWidth, letterSpacing = 0, textWrap = 'word', maxLines = 0, measureText, }) {
17
+ const lines = [];
18
+ if (textWrap === 'none') {
19
+ lines.push(text);
20
+ return lines;
21
+ }
22
+ const words = textWrap === 'word' ? text.split(' ') : text.split('');
23
+ let currentLine = '';
24
+ for (let i = 0; i < words.length; i++) {
25
+ const word = words[i];
26
+ if (!word)
27
+ continue;
28
+ const testLine = currentLine + (currentLine ? (textWrap === 'word' ? ' ' : '') : '') + word;
29
+ // Account for letter spacing
30
+ const charCount = [...testLine].length;
31
+ const extraSpacing = charCount > 1 ? (charCount - 1) * letterSpacing : 0;
32
+ const totalWidth = measureText(testLine) + extraSpacing;
33
+ if (totalWidth > maxWidth && currentLine) {
34
+ lines.push(currentLine);
35
+ currentLine = word;
36
+ if (maxLines && lines.length >= maxLines) {
37
+ break;
38
+ }
39
+ }
40
+ else {
41
+ currentLine = testLine;
42
+ }
43
+ }
44
+ if (currentLine && (!maxLines || lines.length < maxLines)) {
45
+ lines.push(currentLine);
46
+ }
47
+ // Handle overflow
48
+ if (maxLines && lines.length > maxLines) {
49
+ lines.splice(maxLines);
50
+ }
51
+ return lines;
52
+ }
53
+ /**
54
+ * Calculate line width with letter spacing
55
+ */
56
+ function calculateLineWidth({ line, letterSpacing, measureText, }) {
57
+ const chars = [...line];
58
+ let width = 0;
59
+ for (const char of chars) {
60
+ width += measureText(char) + letterSpacing;
61
+ }
62
+ // Remove extra letter spacing after last character
63
+ if (chars.length > 0) {
64
+ width -= letterSpacing;
65
+ }
66
+ return width;
67
+ }
68
+ /**
69
+ * Convert borderRadius value to array format for roundRect()
70
+ * Returns [topLeft, topRight, bottomRight, bottomLeft] or null if no rounding needed
71
+ */
72
+ function getBorderRadii(borderRadius) {
73
+ if (!borderRadius)
74
+ return null;
75
+ if (typeof borderRadius === 'number') {
76
+ if (borderRadius <= 0)
77
+ return null;
78
+ return [borderRadius, borderRadius, borderRadius, borderRadius];
79
+ }
80
+ const topLeft = borderRadius.topLeft ?? 0;
81
+ const topRight = borderRadius.topRight ?? 0;
82
+ const bottomRight = borderRadius.bottomRight ?? 0;
83
+ const bottomLeft = borderRadius.bottomLeft ?? 0;
84
+ // Check if any corner has radius
85
+ if (topLeft <= 0 && topRight <= 0 && bottomRight <= 0 && bottomLeft <= 0) {
86
+ return null;
87
+ }
88
+ return [topLeft, topRight, bottomRight, bottomLeft];
89
+ }
90
+ /**
91
+ * Parse hex color to RGB values
92
+ */
93
+ function parseHexColor(hex) {
94
+ const cleanHex = hex.replace('#', '');
95
+ return {
96
+ r: parseInt(cleanHex.substring(0, 2), 16),
97
+ g: parseInt(cleanHex.substring(2, 4), 16),
98
+ b: parseInt(cleanHex.substring(4, 6), 16),
99
+ };
100
+ }
101
+ /**
102
+ * Convert hex color and opacity to rgba string
103
+ */
104
+ function hexToRgba(hex, opacity = 100) {
105
+ const { r, g, b } = parseHexColor(hex);
106
+ return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
107
+ }
108
+ /**
109
+ * SF Pro tracking table extracted from the font's `trak` table.
110
+ * Maps font sizes to letter-spacing values in pixels.
111
+ * These values are calculated from the font's FUnits using: (FUnits / 2048) * fontSize
112
+ */
113
+ const SF_PRO_TRACKING = {
114
+ 6: 0.240234,
115
+ 9: 0.166992,
116
+ 10: 0.117188,
117
+ 11: 0.064453,
118
+ 12: 0.000000,
119
+ 13: -0.076172,
120
+ 14: -0.150391,
121
+ 15: -0.234375,
122
+ 16: -0.312500,
123
+ 17: -0.431641,
124
+ 20: -0.449219,
125
+ 22: -0.257812,
126
+ 24: 0.070312,
127
+ 28: 0.382812,
128
+ 32: 0.406250,
129
+ 36: 0.369141,
130
+ 50: 0.341797,
131
+ 64: 0.218750,
132
+ 80: 0.000000,
133
+ 100: 0.000000,
134
+ 138: 0.000000,
135
+ };
136
+ /**
137
+ * Get the SF Pro letter-spacing value for a given font size.
138
+ * Uses linear interpolation between known tracking values from the font's trak table.
139
+ * This matches iOS's Core Text rendering behavior.
140
+ *
141
+ * @param fontSize - The font size in pixels
142
+ * @returns The letter-spacing value in pixels
143
+ */
144
+ function getSFProLetterSpacing(fontSize) {
145
+ const sizes = Object.keys(SF_PRO_TRACKING).map(Number).sort((a, b) => a - b);
146
+ // Ensure we have at least one size
147
+ const firstSize = sizes[0];
148
+ const lastSize = sizes[sizes.length - 1];
149
+ if (firstSize === undefined || lastSize === undefined) {
150
+ return 0; // Fallback if no sizes defined
151
+ }
152
+ // Find bounding sizes for interpolation
153
+ let lower = firstSize;
154
+ let upper = lastSize;
155
+ for (let i = 0; i < sizes.length - 1; i++) {
156
+ const current = sizes[i];
157
+ const next = sizes[i + 1];
158
+ if (current !== undefined && next !== undefined && current <= fontSize && next >= fontSize) {
159
+ lower = current;
160
+ upper = next;
161
+ break;
162
+ }
163
+ }
164
+ // Return exact value if below or above range
165
+ const lowerValue = SF_PRO_TRACKING[lower];
166
+ const upperValue = SF_PRO_TRACKING[upper];
167
+ if (lowerValue === undefined || upperValue === undefined) {
168
+ return 0; // Fallback
169
+ }
170
+ if (fontSize <= lower)
171
+ return lowerValue;
172
+ if (fontSize >= upper)
173
+ return upperValue;
174
+ // Linear interpolation
175
+ const t = (fontSize - lower) / (upper - lower);
176
+ return lowerValue + t * (upperValue - lowerValue);
177
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Timeline utility functions for video editor
3
+ *
4
+ * These functions calculate segment positions on the timeline based on
5
+ * the segment/overlay timing model. Used by both the webapp UI and
6
+ * the VideoEditorComposition for rendering.
7
+ */
8
+ import type { VideoEditorSegment, VideoEditorChannel, SegmentTimelinePosition, TimeMode } from '../types/video';
9
+ import type { TimeValue } from '../types/base';
10
+ /**
11
+ * Create a default TimeValue for segment offsets
12
+ */
13
+ export declare function defaultOffset(mode?: TimeMode): TimeValue;
14
+ /**
15
+ * Get base segments (no parentId) for a channel
16
+ * Base segments are the primary timeline elements that overlays attach to
17
+ */
18
+ export declare function getBaseSegments(channel: VideoEditorChannel): VideoEditorSegment[];
19
+ /**
20
+ * Get overlays for a specific parent segment (or global overlays if parentId is null)
21
+ */
22
+ export declare function getOverlays(channel: VideoEditorChannel, parentId: string | null): VideoEditorSegment[];
23
+ /**
24
+ * Calculate segment position on timeline
25
+ *
26
+ * For base segments:
27
+ * - Position is calculated from offset and previous segments
28
+ * - Duration is from the duration property or default (5 seconds)
29
+ *
30
+ * For overlay segments:
31
+ * - Position is calculated relative to parent using relativeStart/relativeEnd
32
+ * - relativeStart/relativeEnd are fractions (0-1) of parent duration
33
+ */
34
+ export declare function getSegmentTimelinePosition(segment: VideoEditorSegment, baseSegments: VideoEditorSegment[], channel: VideoEditorChannel): SegmentTimelinePosition;
35
+ /**
36
+ * Check if a segment is visible at a given time
37
+ */
38
+ export declare function isSegmentVisibleAtTime(segment: VideoEditorSegment, time: number, channel: VideoEditorChannel): boolean;
39
+ /**
40
+ * Calculate estimated total duration based on segments
41
+ */
42
+ export declare function calculateEstimatedDuration(channels: VideoEditorChannel[]): number;
43
+ /**
44
+ * Calculate the timeline content end time (used for both ruler and scroll width)
45
+ */
46
+ export declare function calculateTimelineContentEnd(channel: VideoEditorChannel): number;
47
+ /**
48
+ * Format time in mm:ss.ms
49
+ */
50
+ export declare function formatTime(ms: number): string;
51
+ /**
52
+ * Parse time string to milliseconds
53
+ */
54
+ export declare function parseTime(timeStr: string): number;
55
+ /**
56
+ * Generate a unique segment ID
57
+ */
58
+ export declare function generateSegmentId(): string;
59
+ /**
60
+ * Generate a unique overlay ID
61
+ */
62
+ export declare function generateOverlayId(): string;
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ /**
3
+ * Timeline utility functions for video editor
4
+ *
5
+ * These functions calculate segment positions on the timeline based on
6
+ * the segment/overlay timing model. Used by both the webapp UI and
7
+ * the VideoEditorComposition for rendering.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.defaultOffset = defaultOffset;
11
+ exports.getBaseSegments = getBaseSegments;
12
+ exports.getOverlays = getOverlays;
13
+ exports.getSegmentTimelinePosition = getSegmentTimelinePosition;
14
+ exports.isSegmentVisibleAtTime = isSegmentVisibleAtTime;
15
+ exports.calculateEstimatedDuration = calculateEstimatedDuration;
16
+ exports.calculateTimelineContentEnd = calculateTimelineContentEnd;
17
+ exports.formatTime = formatTime;
18
+ exports.parseTime = parseTime;
19
+ exports.generateSegmentId = generateSegmentId;
20
+ exports.generateOverlayId = generateOverlayId;
21
+ /**
22
+ * Create a default TimeValue for segment offsets
23
+ */
24
+ function defaultOffset(mode = 'flexible') {
25
+ return mode === 'flexible'
26
+ ? { type: 'relative', value: 0 }
27
+ : { type: 'absolute', value: 0 };
28
+ }
29
+ /**
30
+ * Get base segments (no parentId) for a channel
31
+ * Base segments are the primary timeline elements that overlays attach to
32
+ */
33
+ function getBaseSegments(channel) {
34
+ return channel.segments.filter(s => s.parentId === undefined);
35
+ }
36
+ /**
37
+ * Get overlays for a specific parent segment (or global overlays if parentId is null)
38
+ */
39
+ function getOverlays(channel, parentId) {
40
+ return channel.segments.filter(s => s.parentId === (parentId ?? undefined));
41
+ }
42
+ /**
43
+ * Calculate segment position on timeline
44
+ *
45
+ * For base segments:
46
+ * - Position is calculated from offset and previous segments
47
+ * - Duration is from the duration property or default (5 seconds)
48
+ *
49
+ * For overlay segments:
50
+ * - Position is calculated relative to parent using relativeStart/relativeEnd
51
+ * - relativeStart/relativeEnd are fractions (0-1) of parent duration
52
+ */
53
+ function getSegmentTimelinePosition(segment, baseSegments, channel) {
54
+ // For overlays, calculate based on parent
55
+ if (segment.parentId) {
56
+ const parent = channel.segments.find(s => s.id === segment.parentId);
57
+ if (parent) {
58
+ const parentPos = getSegmentTimelinePosition(parent, baseSegments, channel);
59
+ const relStart = segment.relativeStart ?? 0;
60
+ const relEnd = segment.relativeEnd ?? 1;
61
+ return {
62
+ startMs: parentPos.startMs + (parentPos.durationMs * relStart),
63
+ durationMs: parentPos.durationMs * (relEnd - relStart),
64
+ };
65
+ }
66
+ }
67
+ // For base segments, calculate based on order
68
+ const baseIndex = baseSegments.findIndex(s => s.id === segment.id);
69
+ let accumulatedTime = 0;
70
+ for (let i = 0; i < baseIndex; i++) {
71
+ const prev = baseSegments[i];
72
+ if (prev) {
73
+ accumulatedTime += prev.duration?.type === 'absolute'
74
+ ? prev.duration.value
75
+ : 5000; // Default 5 seconds
76
+ }
77
+ }
78
+ const startMs = segment.offset.type === 'absolute'
79
+ ? segment.offset.value
80
+ : accumulatedTime;
81
+ const durationMs = segment.duration?.type === 'absolute'
82
+ ? segment.duration.value
83
+ : 5000; // Default 5 seconds
84
+ return { startMs, durationMs };
85
+ }
86
+ /**
87
+ * Check if a segment is visible at a given time
88
+ */
89
+ function isSegmentVisibleAtTime(segment, time, channel) {
90
+ const baseSegments = getBaseSegments(channel);
91
+ const { startMs, durationMs } = getSegmentTimelinePosition(segment, baseSegments, channel);
92
+ const endMs = startMs + durationMs;
93
+ return time >= startMs && time < endMs;
94
+ }
95
+ /**
96
+ * Calculate estimated total duration based on segments
97
+ */
98
+ function calculateEstimatedDuration(channels) {
99
+ let maxDuration = 5000; // Minimum 5 seconds
100
+ for (const channel of channels) {
101
+ let channelTime = 0;
102
+ for (const segment of channel.segments) {
103
+ // Skip overlays - they don't extend the timeline
104
+ if (segment.parentId)
105
+ continue;
106
+ // For fixed timing, use the actual offset
107
+ if (segment.offset.type === 'absolute') {
108
+ channelTime = segment.offset.value;
109
+ }
110
+ else {
111
+ channelTime += 5000; // Estimate for relative
112
+ }
113
+ if (segment.duration?.type === 'absolute') {
114
+ channelTime += segment.duration.value;
115
+ }
116
+ else {
117
+ channelTime += 5000; // Estimate
118
+ }
119
+ }
120
+ maxDuration = Math.max(maxDuration, channelTime);
121
+ }
122
+ return maxDuration;
123
+ }
124
+ /**
125
+ * Calculate the timeline content end time (used for both ruler and scroll width)
126
+ */
127
+ function calculateTimelineContentEnd(channel) {
128
+ const baseSegments = getBaseSegments(channel);
129
+ let lastEnd = 0;
130
+ for (const segment of baseSegments) {
131
+ const { startMs, durationMs } = getSegmentTimelinePosition(segment, baseSegments, channel);
132
+ lastEnd = Math.max(lastEnd, startMs + durationMs);
133
+ }
134
+ // Add 2 seconds for "Add Segment" button space, round up to nearest second
135
+ return Math.ceil((lastEnd + 2000) / 1000) * 1000;
136
+ }
137
+ /**
138
+ * Format time in mm:ss.ms
139
+ */
140
+ function formatTime(ms) {
141
+ const totalSeconds = Math.floor(ms / 1000);
142
+ const minutes = Math.floor(totalSeconds / 60);
143
+ const seconds = totalSeconds % 60;
144
+ const milliseconds = Math.floor((ms % 1000) / 10);
145
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${milliseconds.toString().padStart(2, '0')}`;
146
+ }
147
+ /**
148
+ * Parse time string to milliseconds
149
+ */
150
+ function parseTime(timeStr) {
151
+ const parts = timeStr.split(':');
152
+ if (parts.length !== 2)
153
+ return 0;
154
+ const [minStr, secPart] = parts;
155
+ const minutes = parseInt(minStr ?? '0', 10) || 0;
156
+ const secParts = (secPart ?? '0').split('.');
157
+ const seconds = parseInt(secParts[0] ?? '0', 10) || 0;
158
+ const ms = parseInt((secParts[1] ?? '0').padEnd(2, '0').slice(0, 2), 10) * 10 || 0;
159
+ return (minutes * 60 + seconds) * 1000 + ms;
160
+ }
161
+ /**
162
+ * Generate a unique segment ID
163
+ */
164
+ function generateSegmentId() {
165
+ return `segment-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
166
+ }
167
+ /**
168
+ * Generate a unique overlay ID
169
+ */
170
+ function generateOverlayId() {
171
+ return `overlay-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
172
+ }
package/dist/render.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ApiResponse } from './types';
2
- import type { ImageEditorNodeConfig, VideoEditorNodeConfig, DeduplicationInput } from 'ugcinc-render';
2
+ import type { ImageEditorNodeConfig, VideoEditorNodeConfig, DeduplicationInput } from './render/types';
3
3
  export interface RenderJobResponse {
4
4
  job_id: string;
5
5
  status: string;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ImageEditorElement, ImageEditorNodeConfig, DimensionPresetKey, VideoEditorNodeConfig, VideoEditorChannel, VideoEditorSegment, VideoEditorVideoSegment, VideoEditorAudioSegment, VideoEditorImageSegment, VideoEditorTextSegment, VideoEditorImageSequenceSegment, VideoEditorVideoSequenceSegment, TimeValue, TimeMode, SegmentTimelinePosition, DeduplicationInput } from 'ugcinc-render';
1
+ import type { ImageEditorElement, ImageEditorNodeConfig, DimensionPresetKey, VideoEditorNodeConfig, VideoEditorChannel, VideoEditorSegment, VideoEditorVideoSegment, VideoEditorAudioSegment, VideoEditorImageSegment, VideoEditorTextSegment, VideoEditorImageSequenceSegment, VideoEditorVideoSequenceSegment, TimeValue, TimeMode, SegmentTimelinePosition, DeduplicationInput } from './render/types';
2
2
  /**
3
3
  * API response types
4
4
  */
@@ -471,6 +471,10 @@ export interface ImageEditorNodeOutput extends Record<string, unknown>, NodeOutp
471
471
  height?: number;
472
472
  aspectRatio?: string;
473
473
  }
474
+ export interface VideoEditorNodeInput extends Record<string, unknown> {
475
+ type: 'video-composer';
476
+ editor: VideoEditorNodeConfig;
477
+ }
474
478
  export interface EditorConfig extends BaseEditorConfig {
475
479
  channels: Array<{
476
480
  id: string;
@@ -751,24 +755,24 @@ export type StaticSegment = ImageSegment | TextSegment;
751
755
  /**
752
756
  * Automation types
753
757
  */
754
- export type MediaType = 'video' | 'image' | 'audio' | 'text' | 'account' | 'date' | 'social_audio' | 'boolean';
758
+ export type MediaType = 'video' | 'image' | 'audio' | 'text';
755
759
  /**
756
760
  * Array types for collections
757
761
  */
758
- export type ArrayType = 'image[]' | 'video[]' | 'audio[]' | 'text[]' | 'object[]' | 'account[]' | 'social_audio[]' | 'boolean[]';
762
+ export type ArrayType = 'image[]' | 'video[]' | 'audio[]' | 'text[]' | 'object[]';
759
763
  /**
760
764
  * Extended port types (includes non-media types for special nodes)
761
765
  */
762
- export type PortType = MediaType | 'number' | 'boolean' | 'object' | ArrayType;
766
+ export type PortType = MediaType | 'number' | 'boolean' | 'object' | 'account' | 'date' | 'social_audio' | ArrayType;
763
767
  /**
764
768
  * Selection order configuration for media nodes
765
769
  */
766
770
  export type SelectionMode = 'random' | 'sequential';
767
771
  export type ExhaustionBehavior = 'restart' | 'error';
768
772
  export interface SelectionConfig {
769
- mode: SelectionMode;
770
- enforceUniqueness: boolean;
771
- exhaustionBehavior: ExhaustionBehavior;
773
+ mode?: SelectionMode;
774
+ enforceUniqueness?: boolean;
775
+ exhaustionBehavior?: ExhaustionBehavior;
772
776
  }
773
777
  export interface SelectionState {
774
778
  usedIndices: number[];
@@ -809,14 +813,26 @@ export interface NodePort {
809
813
  /** Schema of array items (when this port outputs an array) */
810
814
  itemSchema?: Record<string, PropertySchema>;
811
815
  }
816
+ /**
817
+ * Simplified port definition for database storage.
818
+ * Uses string type since PortType union gets serialized to JSON.
819
+ */
820
+ export interface ResolvedPort {
821
+ id: string;
822
+ type: string;
823
+ required: boolean;
824
+ /** Schema of array items (when this port outputs an array) */
825
+ itemSchema?: Record<string, PropertySchema>;
826
+ }
812
827
  /**
813
828
  * Resolved ports for nodes with dynamic inputs/outputs.
814
829
  * Stored in node config when the automation is saved, allowing validation
815
830
  * without needing to resolve ports at validation time.
831
+ * Note: Uses simplified ResolvedPort type for database compatibility.
816
832
  */
817
833
  export interface ResolvedPorts {
818
- inputs: NodePort[];
819
- outputs: NodePort[];
834
+ inputs: ResolvedPort[];
835
+ outputs: ResolvedPort[];
820
836
  }
821
837
  /**
822
838
  * Functional category for automation nodes
@@ -849,7 +865,7 @@ export interface NodeControlConfig {
849
865
  */
850
866
  isInitializer?: boolean;
851
867
  }
852
- export type NodeTypeEnum = 'social-audio' | 'text' | 'media' | 'video-import' | 'image-composer' | 'video-composer' | 'generate-image' | 'generate-video' | 'custom-model' | 'llm' | 'output' | 'manual-trigger' | 'recurrence' | 'compose-workflow' | 'account' | 'auto-post' | 'save-to-media' | 'deduplicate' | 'for-each' | 'random' | 'random-route' | 'branch' | 'if' | 'not' | 'transcript' | 'auto-caption' | 'screenshot-animation' | 'create-dm' | 'collect' | 'destructure';
868
+ export type NodeTypeEnum = 'social-audio' | 'text' | 'media' | 'video-import' | 'image-composer' | 'video-composer' | 'generate-image' | 'generate-video' | 'custom-model' | 'llm' | 'output' | 'manual-trigger' | 'recurrence' | 'compose-workflow' | 'account' | 'auto-post' | 'save-to-media' | 'deduplicate' | 'for-each' | 'for-each-value' | 'random' | 'random-route' | 'branch' | 'if' | 'not' | 'transcript' | 'auto-caption' | 'screenshot-animation' | 'create-dm' | 'collect' | 'destructure';
853
869
  export interface OutputSchemaProperty {
854
870
  type: 'string' | 'number' | 'boolean' | 'array' | 'object';
855
871
  items?: 'string' | 'number' | 'boolean';
@@ -909,6 +925,10 @@ export interface LLMNodeConfig {
909
925
  export type ImageGenerationTextModel = 'fal-ai/gemini-3-pro-image-preview' | 'fal-ai/nano-banana-pro' | 'fal-ai/nano-banana' | 'fal-ai/gpt-image-1/text-to-image';
910
926
  export type ImageGenerationEditModel = 'fal-ai/gemini-3-pro-image-preview/edit' | 'fal-ai/nano-banana-pro/edit' | 'fal-ai/nano-banana/edit' | 'fal-ai/gpt-image-1/edit-image';
911
927
  export type ImageGenerationModel = ImageGenerationTextModel | ImageGenerationEditModel;
928
+ /**
929
+ * Type guard to check if model is an edit/inpaint model
930
+ */
931
+ export declare function isEditModel(model: ImageGenerationModel): model is ImageGenerationEditModel;
912
932
  /**
913
933
  * Image Generation Node Configuration
914
934
  */
@@ -979,13 +999,20 @@ export interface WorkflowNodeDefinition {
979
999
  }
980
1000
  export interface OutputInput {
981
1001
  id: string;
982
- type: 'image' | 'video' | 'audio' | 'text' | 'social_audio' | 'account' | 'boolean' | 'object';
983
- isArray?: boolean;
984
- /** Schema for object types - describes the structure of the object */
985
- itemSchema?: Record<string, PropertySchema>;
1002
+ title: string;
1003
+ type: 'image' | 'video' | 'audio' | 'text';
1004
+ /** Individual tag for this input (overrides global tag) */
1005
+ tag?: string;
986
1006
  }
987
1007
  export interface OutputNodeConfig {
988
1008
  inputs: OutputInput[];
1009
+ /** Which input is the main visual preview for the workflow */
1010
+ mainPreviewInputId?: string;
1011
+ saveToMedia?: boolean;
1012
+ globalTag?: string;
1013
+ makeUnique?: boolean;
1014
+ createPost?: boolean;
1015
+ requireReview?: boolean;
989
1016
  }
990
1017
  /**
991
1018
  * Level 3 (deepest) object schema field - primitives only, no further nesting
@@ -1222,7 +1249,6 @@ export interface AutoPostNodeConfig {
1222
1249
  */
1223
1250
  export interface SaveToMediaInput {
1224
1251
  id: string;
1225
- title: string;
1226
1252
  type: 'image' | 'video' | 'audio' | 'text';
1227
1253
  /** Optional individual tag for this input (overrides global tag) */
1228
1254
  tag?: string;
@@ -1232,11 +1258,11 @@ export interface SaveToMediaInput {
1232
1258
  */
1233
1259
  export interface SaveToMediaNodeConfig {
1234
1260
  /** Dynamic inputs to save */
1235
- inputs?: SaveToMediaInput[];
1261
+ inputs: SaveToMediaInput[];
1236
1262
  /** Global tag applied to all saved items */
1237
1263
  globalTag?: string;
1238
1264
  /** If true, append date in mm-dd-yy format to tag */
1239
- includeDateTag?: boolean;
1265
+ tagWithDate?: boolean;
1240
1266
  /** If true, append run ID to make tags unique */
1241
1267
  makeUnique?: boolean;
1242
1268
  }
@@ -1617,6 +1643,10 @@ export interface DestructureNodeConfig {
1617
1643
  export type VideoGenerationTextToVideoModel = 'fal-ai/veo3.1' | 'fal-ai/veo3' | 'fal-ai/veo3/fast' | 'fal-ai/kling-video/v2.6/pro/text-to-video' | 'fal-ai/kling-video/v2.5/pro/text-to-video' | 'fal-ai/luma-dream-machine/ray-2' | 'fal-ai/luma-dream-machine/ray-2-flash' | 'fal-ai/minimax/hailuo-2.3/pro/text-to-video' | 'wan/v2.6/text-to-video' | 'fal-ai/sora-2/text-to-video';
1618
1644
  export type VideoGenerationImageToVideoModel = 'fal-ai/veo3.1/image-to-video' | 'fal-ai/veo3/image-to-video' | 'fal-ai/kling-video/v2.6/pro/image-to-video' | 'fal-ai/kling-video/v2.5/pro/image-to-video' | 'fal-ai/luma-dream-machine/ray-2/image-to-video' | 'fal-ai/luma-dream-machine/ray-2-flash/image-to-video' | 'fal-ai/minimax/hailuo-2.3/pro/image-to-video' | 'wan/v2.6/image-to-video' | 'fal-ai/sora-2/image-to-video/pro';
1619
1645
  export type VideoGenerationModel = VideoGenerationTextToVideoModel | VideoGenerationImageToVideoModel;
1646
+ /**
1647
+ * Type guard to check if model is an image-to-video model
1648
+ */
1649
+ export declare function isImageToVideoModel(model: VideoGenerationModel): model is VideoGenerationImageToVideoModel;
1620
1650
  /**
1621
1651
  * Video Generation node configuration - AI video generation
1622
1652
  */
@@ -2059,3 +2089,92 @@ export interface AutomationRunExport {
2059
2089
  edges: ExportedEdge[];
2060
2090
  };
2061
2091
  }
2092
+ /**
2093
+ * Account object that flows through edges for posting
2094
+ */
2095
+ export interface AccountData {
2096
+ id: string;
2097
+ username: string;
2098
+ platform: 'tiktok';
2099
+ displayName?: string;
2100
+ profilePicUrl?: string;
2101
+ metadata?: Record<string, unknown>;
2102
+ }
2103
+ /**
2104
+ * Output structure for flow control nodes (if, random-route).
2105
+ * Contains values for selected ports and list of skipped port IDs.
2106
+ */
2107
+ export interface FlowControlOutput {
2108
+ values: Record<string, unknown>;
2109
+ skippedPorts: string[];
2110
+ }
2111
+ /**
2112
+ * Node types array for runtime use
2113
+ */
2114
+ export declare const NodeTypes: readonly ["social-audio", "text", "media", "video-import", "image-composer", "video-composer", "generate-image", "generate-video", "custom-model", "llm", "output", "manual-trigger", "recurrence", "compose-workflow", "account", "auto-post", "save-to-media", "deduplicate", "for-each", "for-each-value", "transcript", "auto-caption", "screenshot-animation", "random", "random-route", "branch", "if", "not", "create-dm", "collect", "destructure"];
2115
+ /**
2116
+ * Node type enum derived from NodeTypes const array
2117
+ * This is the canonical type - use this instead of the string union NodeTypeEnum
2118
+ */
2119
+ export type NodeType = typeof NodeTypes[number];
2120
+ /**
2121
+ * Context passed to executor-based node execution.
2122
+ * Replaces the complex outputMap/iteration/portOffsets pattern with simple inputs.
2123
+ */
2124
+ export interface ExecutorContext {
2125
+ /** Database ID of this executor */
2126
+ executorId: string;
2127
+ /** Run ID this executor belongs to */
2128
+ runId: string;
2129
+ /** Template node ID this executor was created from */
2130
+ templateNodeId: string;
2131
+ /** Index of this executor (0, 1, 2...) for the same template node */
2132
+ executorIndex: number;
2133
+ /** Node type */
2134
+ type: NodeType;
2135
+ /** Node config (includes assignedValue for source nodes) */
2136
+ config: Record<string, unknown>;
2137
+ /** Input values read from incoming edges (portId -> value) */
2138
+ inputs: Record<string, unknown>;
2139
+ }
2140
+ /**
2141
+ * Executor for synchronous nodes.
2142
+ * Much simpler than the old NodeControlConfig - just takes context and returns output.
2143
+ */
2144
+ export interface NodeExecutor<TOutput = unknown> {
2145
+ type: NodeType;
2146
+ /**
2147
+ * Execute the node and return a single output value.
2148
+ * For multi-output nodes (like LLM), return an object with output fields.
2149
+ */
2150
+ execute: (ctx: ExecutorContext) => Promise<TOutput>;
2151
+ }
2152
+ /**
2153
+ * Async job status for executor-based async nodes
2154
+ */
2155
+ export type ExecutorAsyncJobStatus<TOutput = unknown> = {
2156
+ status: 'pending';
2157
+ } | {
2158
+ status: 'completed';
2159
+ output: TOutput;
2160
+ } | {
2161
+ status: 'failed';
2162
+ error: string;
2163
+ };
2164
+ /**
2165
+ * Executor for asynchronous nodes (video-editor, generate-image, workflow).
2166
+ * Uses submit/poll pattern for durable workflows.
2167
+ */
2168
+ export interface AsyncNodeExecutor<TOutput = unknown> extends Omit<NodeExecutor<TOutput>, 'execute'> {
2169
+ isAsync: true;
2170
+ /** Submit job - returns job ID for polling */
2171
+ submitJob: (ctx: ExecutorContext) => Promise<{
2172
+ jobId: string;
2173
+ }>;
2174
+ /** Check job status - called by orchestrator with workflow sleep between calls */
2175
+ checkStatus: (jobId: string, ctx: ExecutorContext) => Promise<ExecutorAsyncJobStatus<TOutput>>;
2176
+ }
2177
+ /**
2178
+ * Type guard to check if an executor is async
2179
+ */
2180
+ export declare function isAsyncExecutor<T>(executor: NodeExecutor<T> | AsyncNodeExecutor<T>): executor is AsyncNodeExecutor<T>;