ugcinc 4.5.43 → 4.5.44

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.
@@ -48,6 +48,7 @@ declare const definition: import("./types").NodeDefinition<"create-dm", "generat
48
48
  imessageMessageHeaderTimestampText: string;
49
49
  imessageMessageHeaderTimeInputType: InputType;
50
50
  instagramUserHasStory: boolean;
51
+ dynamicCrop: import("../../render/types/crop").DynamicCropConfig | undefined;
51
52
  outputMode: OutputMode;
52
53
  selectionMode: SelectionMode | null;
53
54
  }, CreateDmNodeInputs, CreateDmNodeOutputs, false>;
@@ -42,6 +42,8 @@ const definition = (0, types_1.defineNode)({
42
42
  imessageMessageHeaderTimeInputType: 'static',
43
43
  // Instagram-specific
44
44
  instagramUserHasStory: false,
45
+ // Dynamic crop - crop output to DM element bounds
46
+ dynamicCrop: undefined,
45
47
  outputMode: 'per-input',
46
48
  selectionMode: null,
47
49
  },
@@ -110,6 +110,7 @@ export declare const nodeDefinitions: {
110
110
  imessageMessageHeaderTimestampText: string;
111
111
  imessageMessageHeaderTimeInputType: import("./types").InputType;
112
112
  instagramUserHasStory: boolean;
113
+ dynamicCrop: import("../..").DynamicCropConfig | undefined;
113
114
  outputMode: import("./types").OutputMode;
114
115
  selectionMode: import("./types").SelectionMode | null;
115
116
  }, import("./create-dm").CreateDmNodeInputs, import("./create-dm").CreateDmNodeOutputs, false> & {
@@ -9,8 +9,10 @@ const VideoEditorComposition_1 = require("./compositions/VideoEditorComposition"
9
9
  const AutoCaptionComposition_1 = require("./compositions/AutoCaptionComposition");
10
10
  const ScreenshotAnimation_1 = require("./compositions/ScreenshotAnimation");
11
11
  const InstagramDmComposition_1 = require("./compositions/InstagramDmComposition");
12
+ const convertPropsToElements_1 = require("./compositions/InstagramDmComposition/convertPropsToElements");
12
13
  const IMessageDmComposition_1 = require("./compositions/IMessageDmComposition");
13
14
  const types_1 = require("./compositions/IMessageDmComposition/types");
15
+ const convertPropsToElements_2 = require("./compositions/IMessageDmComposition/convertPropsToElements");
14
16
  const positionResolver_1 = require("./utils/positionResolver");
15
17
  const cropBounds_1 = require("./utils/cropBounds");
16
18
  const fonts_1 = require("./utils/fonts");
@@ -427,6 +429,8 @@ const instagramDmSchema = zod_1.z.object({
427
429
  // Divider line
428
430
  dividerLineY: zod_1.z.number().optional().default(353).describe('Divider line Y position'),
429
431
  dividerLineColor: zod_1.z.string().optional().default('#25282d').describe('Divider line color'),
432
+ // Dynamic crop - crop output to element bounds
433
+ dynamicCrop: zod_1.z.record(zod_1.z.any()).optional().describe('Dynamic crop configuration'),
430
434
  });
431
435
  // Cast components to work around renderer's strict typing
432
436
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -501,8 +505,73 @@ const calculateImageMetadata = async ({ props }) => {
501
505
  durationInFrames: 1,
502
506
  };
503
507
  };
508
+ /**
509
+ * calculateMetadata for InstagramDmComposition
510
+ * Computes output dimensions when dynamic crop is enabled.
511
+ */
512
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
513
+ const calculateInstagramDmMetadata = async ({ props }) => {
514
+ const canvasWidth = props.width ?? 1206;
515
+ const canvasHeight = props.height ?? 2622;
516
+ const defaultResult = { width: canvasWidth, height: canvasHeight, fps: 30, durationInFrames: 90 };
517
+ if (!(0, cropBounds_1.isDynamicCropEnabled)(props.dynamicCrop)) {
518
+ return defaultResult;
519
+ }
520
+ try {
521
+ await (0, fonts_1.preloadFonts)();
522
+ await new Promise(resolve => setTimeout(resolve, 50));
523
+ }
524
+ catch (err) {
525
+ console.error('[calculateInstagramDmMetadata] Font loading failed:', err);
526
+ return defaultResult;
527
+ }
528
+ const { elements } = (0, convertPropsToElements_1.convertPropsToElements)(props);
529
+ const { elements: resolved } = (0, positionResolver_1.resolveElementPositions)(elements, {});
530
+ if (!resolved)
531
+ return defaultResult;
532
+ const cropBounds = (0, cropBounds_1.calculateCropBounds)(resolved, props.dynamicCrop, canvasWidth, canvasHeight);
533
+ return {
534
+ width: Math.round(cropBounds.width),
535
+ height: Math.round(cropBounds.height),
536
+ fps: 30,
537
+ durationInFrames: 90,
538
+ };
539
+ };
540
+ /**
541
+ * calculateMetadata for IMessageDmComposition
542
+ * Computes output dimensions when dynamic crop is enabled.
543
+ */
544
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
545
+ const calculateIMessageDmMetadata = async ({ props }) => {
546
+ const canvasWidth = props.width ?? 1206;
547
+ const canvasHeight = props.height ?? 2622;
548
+ const defaultResult = { width: canvasWidth, height: canvasHeight, fps: 30, durationInFrames: 90 };
549
+ if (!(0, cropBounds_1.isDynamicCropEnabled)(props.dynamicCrop)) {
550
+ return defaultResult;
551
+ }
552
+ try {
553
+ await (0, fonts_1.preloadFonts)();
554
+ await new Promise(resolve => setTimeout(resolve, 50));
555
+ }
556
+ catch (err) {
557
+ console.error('[calculateIMessageDmMetadata] Font loading failed:', err);
558
+ return defaultResult;
559
+ }
560
+ const mergedProps = { ...IMessageDmComposition_1.defaultIMessageDmProps, ...props };
561
+ const { elements } = (0, convertPropsToElements_2.convertPropsToElements)(mergedProps);
562
+ const { elements: resolved } = (0, positionResolver_1.resolveElementPositions)(elements, {});
563
+ if (!resolved)
564
+ return defaultResult;
565
+ const cropBounds = (0, cropBounds_1.calculateCropBounds)(resolved, props.dynamicCrop, canvasWidth, canvasHeight);
566
+ return {
567
+ width: Math.round(cropBounds.width),
568
+ height: Math.round(cropBounds.height),
569
+ fps: 30,
570
+ durationInFrames: 90,
571
+ };
572
+ };
504
573
  const RenderRoot = () => {
505
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "ImageEditorComposition", component: ImageComp, calculateMetadata: calculateImageMetadata, durationInFrames: 1, fps: 30, width: 1080, height: 1920, defaultProps: defaultImageProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "VideoEditorComposition", component: VideoComp, durationInFrames: 150, fps: 30, width: 1080, height: 1920, defaultProps: defaultVideoProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "AutoCaptionComposition", component: AutoCaptionComp, durationInFrames: 300, fps: 30, width: 1080, height: 1920, defaultProps: defaultAutoCaptionProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "ScreenshotAnimationComposition", component: ScreenshotAnimationComp, schema: screenshotAnimationSchema, durationInFrames: 180, fps: 59, width: 1206, height: 2622, defaultProps: defaultScreenshotAnimationProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "InstagramDmComposition", component: InstagramDmComp, schema: instagramDmSchema, durationInFrames: 90, fps: 30, width: 1206, height: 2622, defaultProps: InstagramDmComposition_1.defaultInstagramDmProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "IMessageDmComposition", component: IMessageDmComp, schema: types_1.iMessageDmPropsSchema, durationInFrames: 90, fps: 30, width: 1206, height: 2622, defaultProps: IMessageDmComposition_1.defaultIMessageDmProps })] }));
574
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "ImageEditorComposition", component: ImageComp, calculateMetadata: calculateImageMetadata, durationInFrames: 1, fps: 30, width: 1080, height: 1920, defaultProps: defaultImageProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "VideoEditorComposition", component: VideoComp, durationInFrames: 150, fps: 30, width: 1080, height: 1920, defaultProps: defaultVideoProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "AutoCaptionComposition", component: AutoCaptionComp, durationInFrames: 300, fps: 30, width: 1080, height: 1920, defaultProps: defaultAutoCaptionProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "ScreenshotAnimationComposition", component: ScreenshotAnimationComp, schema: screenshotAnimationSchema, durationInFrames: 180, fps: 59, width: 1206, height: 2622, defaultProps: defaultScreenshotAnimationProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "InstagramDmComposition", component: InstagramDmComp, calculateMetadata: calculateInstagramDmMetadata, schema: instagramDmSchema, durationInFrames: 90, fps: 30, width: 1206, height: 2622, defaultProps: InstagramDmComposition_1.defaultInstagramDmProps }), (0, jsx_runtime_1.jsx)(remotion_1.Composition, { id: "IMessageDmComposition", component: IMessageDmComp, calculateMetadata: calculateIMessageDmMetadata, schema: types_1.iMessageDmPropsSchema, durationInFrames: 90, fps: 30, width: 1206, height: 2622, defaultProps: IMessageDmComposition_1.defaultIMessageDmProps })] }));
506
575
  };
507
576
  exports.RenderRoot = RenderRoot;
508
577
  exports.default = exports.RenderRoot;
@@ -6,11 +6,14 @@
6
6
  * font loading, and shared iOS chrome (status bar, home indicator).
7
7
  */
8
8
  import React from 'react';
9
+ import type { CropBounds } from '../../types/crop';
9
10
  export interface BaseDmCompositionProps {
10
11
  /** Canvas width */
11
12
  width: number;
12
13
  /** Canvas height */
13
14
  height: number;
15
+ /** When set, crop output to these bounds (for dynamic crop) */
16
+ cropBounds?: CropBounds;
14
17
  /** Background color */
15
18
  backgroundColor: string;
16
19
  /** Show debug overlay with crosshair and coordinates */
@@ -108,5 +111,5 @@ export interface BaseDmCompositionProps {
108
111
  /** Status bar elements color (time, cell bars, icons) - defaults to white */
109
112
  statusBarColor?: string;
110
113
  }
111
- export declare function BaseDmComposition({ width, height, backgroundColor, showDebugOverlay, referenceImageUrl, showReferenceImage, referenceOpacity, currentProps, children, time, timeRight, timeBottom, timeFontSize, timeLetterSpacing, timeColor, noNotisTop, noNotisBottom, noNotisLeft, noNotisRight, cellLevel, cell1Top, cell1Bottom, cell1Left, cell1Right, cell2Top, cell2Bottom, cell2Left, cell2Right, cell3Top, cell3Bottom, cell3Left, cell3Right, cell4Top, cell4Bottom, cell4Left, cell4Right, wifiTop, wifiBottom, wifiLeft, wifiRight, batteryTop, batteryBottom, batteryLeft, batteryRight, homeIndicatorTop, homeIndicatorBottom, homeIndicatorLeft, homeIndicatorRight, homeIndicatorColor, statusBarColor, }: BaseDmCompositionProps): import("react/jsx-runtime").JSX.Element | null;
114
+ export declare function BaseDmComposition({ width, height, cropBounds, backgroundColor, showDebugOverlay, referenceImageUrl, showReferenceImage, referenceOpacity, currentProps, children, time, timeRight, timeBottom, timeFontSize, timeLetterSpacing, timeColor, noNotisTop, noNotisBottom, noNotisLeft, noNotisRight, cellLevel, cell1Top, cell1Bottom, cell1Left, cell1Right, cell2Top, cell2Bottom, cell2Left, cell2Right, cell3Top, cell3Bottom, cell3Left, cell3Right, cell4Top, cell4Bottom, cell4Left, cell4Right, wifiTop, wifiBottom, wifiLeft, wifiRight, batteryTop, batteryBottom, batteryLeft, batteryRight, homeIndicatorTop, homeIndicatorBottom, homeIndicatorLeft, homeIndicatorRight, homeIndicatorColor, statusBarColor, }: BaseDmCompositionProps): import("react/jsx-runtime").JSX.Element | null;
112
115
  export default BaseDmComposition;
@@ -13,7 +13,7 @@ const react_1 = require("react");
13
13
  const remotion_1 = require("remotion");
14
14
  const fonts_1 = require("../../utils/fonts");
15
15
  const DebugOverlay_1 = require("./DebugOverlay");
16
- function BaseDmComposition({ width, height, backgroundColor, showDebugOverlay = false, referenceImageUrl, showReferenceImage = false, referenceOpacity = 50, currentProps, children,
16
+ function BaseDmComposition({ width, height, cropBounds, backgroundColor, showDebugOverlay = false, referenceImageUrl, showReferenceImage = false, referenceOpacity = 50, currentProps, children,
17
17
  // Status bar - Time (defaults from IG DM)
18
18
  time = '9:41', timeRight = 262, timeBottom = 125, timeFontSize = 52, timeLetterSpacing = 1.6, timeColor = '#ffffff',
19
19
  // Status bar - No notifications
@@ -75,6 +75,11 @@ statusBarColor = '#ffffff', }) {
75
75
  setReferenceVisible((prev) => !prev);
76
76
  }, []);
77
77
  const zoomScale = zoom / 100;
78
+ // Crop offset when dynamic crop is applied
79
+ const cropOffsetX = cropBounds?.x ?? 0;
80
+ const cropOffsetY = cropBounds?.y ?? 0;
81
+ const contentWidth = cropBounds ? cropBounds.width : width;
82
+ const contentHeight = cropBounds ? cropBounds.height : height;
78
83
  // Determine if we need to invert icons for dark status bar color
79
84
  // If statusBarColor is black or very dark, invert the white icons
80
85
  const isStatusBarDark = statusBarColor === '#000000' || statusBarColor === '#000' || statusBarColor.toLowerCase() === 'black';
@@ -89,124 +94,131 @@ statusBarColor = '#ffffff', }) {
89
94
  return ((0, jsx_runtime_1.jsxs)(remotion_1.AbsoluteFill, { style: {
90
95
  backgroundColor,
91
96
  overflow: 'hidden',
92
- }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
97
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: {
93
98
  position: 'absolute',
94
99
  top: 0,
95
100
  left: 0,
96
- width: width,
97
- height: height,
98
- transform: `translate(${-scrollX}px, ${-scrollY}px) scale(${zoomScale})`,
99
- transformOrigin: 'top left',
100
- }, children: [children, (0, jsx_runtime_1.jsx)("div", { style: {
101
- position: 'absolute',
102
- top: timeBottom - timeFontSize,
103
- left: 0,
104
- width: timeRight,
105
- height: timeFontSize,
106
- display: 'flex',
107
- alignItems: 'center',
108
- justifyContent: 'flex-end',
109
- fontFamily: '"SF Pro", "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif',
110
- fontWeight: 600,
111
- fontSize: timeFontSize,
112
- letterSpacing: timeLetterSpacing,
113
- color: statusBarColor,
114
- zIndex: 30,
115
- pointerEvents: 'none',
116
- }, children: time }), (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: (0, remotion_1.staticFile)('ig-no-notis.png'), style: {
117
- position: 'absolute',
118
- top: noNotisTop,
119
- left: noNotisLeft,
120
- width: noNotisRight - noNotisLeft,
121
- height: noNotisBottom - noNotisTop,
122
- objectFit: 'fill',
123
- pointerEvents: 'none',
124
- zIndex: 50,
125
- filter: iconFilter,
126
- } }), (0, jsx_runtime_1.jsx)("div", { style: {
127
- position: 'absolute',
128
- top: cell1Top,
129
- left: cell1Left,
130
- width: cell1Right - cell1Left,
131
- height: cell1Bottom - cell1Top,
132
- backgroundColor: cellLevel >= 1 ? cellBarActiveColor : cellBarInactiveColor,
133
- borderRadius: 2,
134
- pointerEvents: 'none',
135
- zIndex: 50,
136
- } }), (0, jsx_runtime_1.jsx)("div", { style: {
137
- position: 'absolute',
138
- top: cell2Top,
139
- left: cell2Left,
140
- width: cell2Right - cell2Left,
141
- height: cell2Bottom - cell2Top,
142
- backgroundColor: cellLevel >= 2 ? cellBarActiveColor : cellBarInactiveColor,
143
- borderRadius: 2,
144
- pointerEvents: 'none',
145
- zIndex: 50,
146
- } }), (0, jsx_runtime_1.jsx)("div", { style: {
147
- position: 'absolute',
148
- top: cell3Top,
149
- left: cell3Left,
150
- width: cell3Right - cell3Left,
151
- height: cell3Bottom - cell3Top,
152
- backgroundColor: cellLevel >= 3 ? cellBarActiveColor : cellBarInactiveColor,
153
- borderRadius: 2,
154
- pointerEvents: 'none',
155
- zIndex: 50,
156
- } }), (0, jsx_runtime_1.jsx)("div", { style: {
157
- position: 'absolute',
158
- top: cell4Top,
159
- left: cell4Left,
160
- width: cell4Right - cell4Left,
161
- height: cell4Bottom - cell4Top,
162
- backgroundColor: cellLevel >= 4 ? cellBarActiveColor : cellBarInactiveColor,
163
- borderRadius: 2,
164
- pointerEvents: 'none',
165
- zIndex: 50,
166
- } }), (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: (0, remotion_1.staticFile)('ig-wifi.png'), style: {
167
- position: 'absolute',
168
- top: wifiTop,
169
- left: wifiLeft,
170
- width: wifiRight - wifiLeft,
171
- height: wifiBottom - wifiTop,
172
- objectFit: 'fill',
173
- pointerEvents: 'none',
174
- zIndex: 50,
175
- filter: iconFilter,
176
- } }), (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: (0, remotion_1.staticFile)('ig-battery.png'), style: {
177
- position: 'absolute',
178
- top: batteryTop,
179
- left: batteryLeft,
180
- width: batteryRight - batteryLeft,
181
- height: batteryBottom - batteryTop,
182
- objectFit: 'fill',
183
- pointerEvents: 'none',
184
- zIndex: 50,
185
- filter: iconFilter,
186
- } }), (0, jsx_runtime_1.jsx)("div", { style: {
187
- position: 'absolute',
188
- top: homeIndicatorTop,
189
- left: homeIndicatorLeft,
190
- width: homeIndicatorRight - homeIndicatorLeft,
191
- height: homeIndicatorBottom - homeIndicatorTop,
192
- backgroundColor: homeIndicatorColor,
193
- borderRadius: (homeIndicatorBottom - homeIndicatorTop) / 2,
194
- pointerEvents: 'none',
195
- zIndex: 50,
196
- } }), referenceImageUrl && showReferenceImage && (!showDebugOverlay || referenceVisible) && ((0, jsx_runtime_1.jsx)("div", { style: {
197
- position: 'absolute',
198
- top: 0,
199
- left: 0,
200
- right: 0,
201
- bottom: 0,
202
- zIndex: 100,
203
- pointerEvents: 'none',
204
- opacity: clampedReferenceOpacity / 100,
205
- }, children: (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: referenceImageUrl, style: {
206
- width: '100%',
207
- height: '100%',
208
- objectFit: 'cover',
209
- imageRendering: 'pixelated',
210
- } }) }))] }), showDebugOverlay && ((0, jsx_runtime_1.jsx)(DebugOverlay_1.DebugOverlay, { width: width, height: height, zoom: zoom, scrollX: scrollX, scrollY: scrollY, referenceVisible: referenceVisible, onZoomChange: handleZoomChange, onToggleReference: handleToggleReference, currentProps: currentProps }))] }));
101
+ width: contentWidth,
102
+ height: contentHeight,
103
+ overflow: 'hidden',
104
+ }, children: (0, jsx_runtime_1.jsxs)("div", { style: {
105
+ position: 'absolute',
106
+ top: -cropOffsetY,
107
+ left: -cropOffsetX,
108
+ width: width,
109
+ height: height,
110
+ transform: `translate(${-scrollX}px, ${-scrollY}px) scale(${zoomScale})`,
111
+ transformOrigin: 'top left',
112
+ }, children: [children, (0, jsx_runtime_1.jsx)("div", { style: {
113
+ position: 'absolute',
114
+ top: timeBottom - timeFontSize,
115
+ left: 0,
116
+ width: timeRight,
117
+ height: timeFontSize,
118
+ display: 'flex',
119
+ alignItems: 'center',
120
+ justifyContent: 'flex-end',
121
+ fontFamily: '"SF Pro", "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif',
122
+ fontWeight: 600,
123
+ fontSize: timeFontSize,
124
+ letterSpacing: timeLetterSpacing,
125
+ color: statusBarColor,
126
+ zIndex: 30,
127
+ pointerEvents: 'none',
128
+ }, children: time }), (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: (0, remotion_1.staticFile)('ig-no-notis.png'), style: {
129
+ position: 'absolute',
130
+ top: noNotisTop,
131
+ left: noNotisLeft,
132
+ width: noNotisRight - noNotisLeft,
133
+ height: noNotisBottom - noNotisTop,
134
+ objectFit: 'fill',
135
+ pointerEvents: 'none',
136
+ zIndex: 50,
137
+ filter: iconFilter,
138
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
139
+ position: 'absolute',
140
+ top: cell1Top,
141
+ left: cell1Left,
142
+ width: cell1Right - cell1Left,
143
+ height: cell1Bottom - cell1Top,
144
+ backgroundColor: cellLevel >= 1 ? cellBarActiveColor : cellBarInactiveColor,
145
+ borderRadius: 2,
146
+ pointerEvents: 'none',
147
+ zIndex: 50,
148
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
149
+ position: 'absolute',
150
+ top: cell2Top,
151
+ left: cell2Left,
152
+ width: cell2Right - cell2Left,
153
+ height: cell2Bottom - cell2Top,
154
+ backgroundColor: cellLevel >= 2 ? cellBarActiveColor : cellBarInactiveColor,
155
+ borderRadius: 2,
156
+ pointerEvents: 'none',
157
+ zIndex: 50,
158
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
159
+ position: 'absolute',
160
+ top: cell3Top,
161
+ left: cell3Left,
162
+ width: cell3Right - cell3Left,
163
+ height: cell3Bottom - cell3Top,
164
+ backgroundColor: cellLevel >= 3 ? cellBarActiveColor : cellBarInactiveColor,
165
+ borderRadius: 2,
166
+ pointerEvents: 'none',
167
+ zIndex: 50,
168
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
169
+ position: 'absolute',
170
+ top: cell4Top,
171
+ left: cell4Left,
172
+ width: cell4Right - cell4Left,
173
+ height: cell4Bottom - cell4Top,
174
+ backgroundColor: cellLevel >= 4 ? cellBarActiveColor : cellBarInactiveColor,
175
+ borderRadius: 2,
176
+ pointerEvents: 'none',
177
+ zIndex: 50,
178
+ } }), (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: (0, remotion_1.staticFile)('ig-wifi.png'), style: {
179
+ position: 'absolute',
180
+ top: wifiTop,
181
+ left: wifiLeft,
182
+ width: wifiRight - wifiLeft,
183
+ height: wifiBottom - wifiTop,
184
+ objectFit: 'fill',
185
+ pointerEvents: 'none',
186
+ zIndex: 50,
187
+ filter: iconFilter,
188
+ } }), (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: (0, remotion_1.staticFile)('ig-battery.png'), style: {
189
+ position: 'absolute',
190
+ top: batteryTop,
191
+ left: batteryLeft,
192
+ width: batteryRight - batteryLeft,
193
+ height: batteryBottom - batteryTop,
194
+ objectFit: 'fill',
195
+ pointerEvents: 'none',
196
+ zIndex: 50,
197
+ filter: iconFilter,
198
+ } }), (0, jsx_runtime_1.jsx)("div", { style: {
199
+ position: 'absolute',
200
+ top: homeIndicatorTop,
201
+ left: homeIndicatorLeft,
202
+ width: homeIndicatorRight - homeIndicatorLeft,
203
+ height: homeIndicatorBottom - homeIndicatorTop,
204
+ backgroundColor: homeIndicatorColor,
205
+ borderRadius: (homeIndicatorBottom - homeIndicatorTop) / 2,
206
+ pointerEvents: 'none',
207
+ zIndex: 50,
208
+ } }), referenceImageUrl && showReferenceImage && (!showDebugOverlay || referenceVisible) && ((0, jsx_runtime_1.jsx)("div", { style: {
209
+ position: 'absolute',
210
+ top: 0,
211
+ left: 0,
212
+ right: 0,
213
+ bottom: 0,
214
+ zIndex: 100,
215
+ pointerEvents: 'none',
216
+ opacity: clampedReferenceOpacity / 100,
217
+ }, children: (0, jsx_runtime_1.jsx)(remotion_1.Img, { src: referenceImageUrl, style: {
218
+ width: '100%',
219
+ height: '100%',
220
+ objectFit: 'cover',
221
+ imageRendering: 'pixelated',
222
+ } }) }))] }) }), showDebugOverlay && ((0, jsx_runtime_1.jsx)(DebugOverlay_1.DebugOverlay, { width: contentWidth, height: contentHeight, zoom: zoom, scrollX: scrollX, scrollY: scrollY, referenceVisible: referenceVisible, onZoomChange: handleZoomChange, onToggleReference: handleToggleReference, currentProps: currentProps }))] }));
211
223
  }
212
224
  exports.default = BaseDmComposition;
@@ -15,6 +15,8 @@ const ImageEditorComposition_1 = require("../ImageEditorComposition");
15
15
  const DmComposition_1 = require("../DmComposition");
16
16
  const convertPropsToElements_1 = require("./convertPropsToElements");
17
17
  const fonts_1 = require("../../utils/fonts");
18
+ const positionResolver_1 = require("../../utils/positionResolver");
19
+ const cropBounds_1 = require("../../utils/cropBounds");
18
20
  /**
19
21
  * Default composition props for registration
20
22
  */
@@ -286,6 +288,20 @@ const IMessageDmComposition = (props) => {
286
288
  };
287
289
  return (0, convertPropsToElements_1.convertPropsToElements)(propsWithEffectiveColors);
288
290
  }, [mergedProps, fontsLoaded]);
291
+ // Resolve element positions and compute crop bounds when dynamic crop enabled
292
+ const cropBounds = (0, react_1.useMemo)(() => {
293
+ const elements = conversionResult.elements;
294
+ if (!elements.length || !(0, cropBounds_1.isDynamicCropEnabled)(mergedProps.dynamicCrop)) {
295
+ return undefined;
296
+ }
297
+ const result = (0, positionResolver_1.resolveElementPositions)(elements, {});
298
+ const resolved = result.elements;
299
+ if (!resolved)
300
+ return undefined;
301
+ const w = mergedProps.width ?? 1206;
302
+ const h = mergedProps.height ?? 2622;
303
+ return (0, cropBounds_1.calculateCropBounds)(resolved, mergedProps.dynamicCrop, w, h);
304
+ }, [conversionResult.elements, mergedProps.dynamicCrop, mergedProps.width, mergedProps.height]);
289
305
  // Extract values with defaults
290
306
  const { width = 1206, height = 2622, backgroundColor = '#000000', headerBackgroundColor = '#171717', headerBottom = 424, lightMode = false,
291
307
  // Profile section
@@ -318,7 +334,7 @@ const IMessageDmComposition = (props) => {
318
334
  const effectiveUsernameColor = lightMode ? '#000000' : usernameColor;
319
335
  const effectiveHomeIndicatorColor = lightMode ? '#000000' : homeIndicatorColor;
320
336
  const effectiveStatusBarColor = lightMode ? '#000000' : '#ffffff';
321
- return ((0, jsx_runtime_1.jsxs)(DmComposition_1.BaseDmComposition, { width: width, height: height, backgroundColor: effectiveBackgroundColor, showDebugOverlay: showDebugOverlay, referenceImageUrl: referenceImageUrl, showReferenceImage: showReferenceImage, referenceOpacity: referenceOpacity, currentProps: { ...mergedProps },
337
+ return ((0, jsx_runtime_1.jsxs)(DmComposition_1.BaseDmComposition, { width: width, height: height, cropBounds: cropBounds, backgroundColor: effectiveBackgroundColor, showDebugOverlay: showDebugOverlay, referenceImageUrl: referenceImageUrl, showReferenceImage: showReferenceImage, referenceOpacity: referenceOpacity, currentProps: { ...mergedProps },
322
338
  // Status bar props
323
339
  time: time, timeRight: timeRight, timeBottom: timeBottom, timeFontSize: timeFontSize, timeLetterSpacing: timeLetterSpacing, noNotisTop: noNotisTop, noNotisBottom: noNotisBottom, noNotisLeft: noNotisLeft, noNotisRight: noNotisRight, cellLevel: cellLevel, cell1Top: cell1Top, cell1Bottom: cell1Bottom, cell1Left: cell1Left, cell1Right: cell1Right, cell2Top: cell2Top, cell2Bottom: cell2Bottom, cell2Left: cell2Left, cell2Right: cell2Right, cell3Top: cell3Top, cell3Bottom: cell3Bottom, cell3Left: cell3Left, cell3Right: cell3Right, cell4Top: cell4Top, cell4Bottom: cell4Bottom, cell4Left: cell4Left, cell4Right: cell4Right, wifiTop: wifiTop, wifiBottom: wifiBottom, wifiLeft: wifiLeft, wifiRight: wifiRight, batteryTop: batteryTop, batteryBottom: batteryBottom, batteryLeft: batteryLeft, batteryRight: batteryRight,
324
340
  // Home indicator props
@@ -4,6 +4,7 @@
4
4
  * Types for the iMessage DM conversation composition.
5
5
  */
6
6
  import { z } from 'zod';
7
+ import type { DynamicCropConfig } from '../../types/crop';
7
8
  /** Message sender type */
8
9
  export type ImMessageSender = 'user' | 'recipient';
9
10
  /** Message in the conversation */
@@ -194,6 +195,8 @@ export interface IMessageDmCompositionProps {
194
195
  readReceiptLeft?: number;
195
196
  readReceiptRight?: number;
196
197
  messages?: ImMessage[];
198
+ /** Dynamic cropping configuration - crop output to DM element bounds */
199
+ dynamicCrop?: DynamicCropConfig;
197
200
  }
198
201
  /**
199
202
  * Zod schema for iMessage DM composition props (for Remotion)
@@ -391,6 +394,7 @@ export declare const iMessageDmPropsSchema: z.ZodObject<{
391
394
  imageUrl?: string | undefined;
392
395
  groupWithPrevious?: boolean | undefined;
393
396
  }>, "many">>;
397
+ dynamicCrop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
394
398
  }, "strip", z.ZodTypeAny, {
395
399
  backgroundColor?: string | undefined;
396
400
  lightMode?: boolean | undefined;
@@ -404,6 +408,7 @@ export declare const iMessageDmPropsSchema: z.ZodObject<{
404
408
  imageUrl?: string | undefined;
405
409
  groupWithPrevious?: boolean | undefined;
406
410
  }[] | undefined;
411
+ dynamicCrop?: Record<string, any> | undefined;
407
412
  width?: number | undefined;
408
413
  height?: number | undefined;
409
414
  fps?: number | undefined;
@@ -585,6 +590,7 @@ export declare const iMessageDmPropsSchema: z.ZodObject<{
585
590
  imageUrl?: string | undefined;
586
591
  groupWithPrevious?: boolean | undefined;
587
592
  }[] | undefined;
593
+ dynamicCrop?: Record<string, any> | undefined;
588
594
  width?: number | undefined;
589
595
  height?: number | undefined;
590
596
  fps?: number | undefined;
@@ -222,4 +222,6 @@ exports.iMessageDmPropsSchema = zod_1.z.object({
222
222
  imageUrl: zod_1.z.string().optional(),
223
223
  groupWithPrevious: zod_1.z.boolean().optional(),
224
224
  })).optional(),
225
+ // Dynamic crop - crop output to element bounds
226
+ dynamicCrop: zod_1.z.record(zod_1.z.any()).optional(),
225
227
  });
@@ -126,6 +126,8 @@ function convertPublicToProps(input) {
126
126
  // Disable debug/reference overlays for production renders
127
127
  showDebugOverlay: false,
128
128
  showReferenceImage: false,
129
+ // Pass through dynamic crop if provided
130
+ dynamicCrop: input.dynamicCrop,
129
131
  };
130
132
  return props;
131
133
  }
@@ -16,6 +16,8 @@ const ImageEditorComposition_1 = require("../ImageEditorComposition");
16
16
  const DmComposition_1 = require("../DmComposition");
17
17
  const theme_1 = require("./theme");
18
18
  const convertPropsToElements_1 = require("./convertPropsToElements");
19
+ const positionResolver_1 = require("../../utils/positionResolver");
20
+ const cropBounds_1 = require("../../utils/cropBounds");
19
21
  /**
20
22
  * Default composition props for registration
21
23
  */
@@ -268,11 +270,20 @@ function InstagramDmComposition(props) {
268
270
  };
269
271
  return (0, convertPropsToElements_1.convertPropsToElements)(propsWithThemeColors);
270
272
  }, [props, isLightMode, colors.recipientBubble, colors.headerBg, colors.divider, colors.footerBg]);
273
+ // Resolve element positions and compute crop bounds when dynamic crop enabled
274
+ const cropBounds = (0, react_1.useMemo)(() => {
275
+ const result = (0, positionResolver_1.resolveElementPositions)(elements, {});
276
+ const resolved = result.elements;
277
+ if (!(0, cropBounds_1.isDynamicCropEnabled)(props.dynamicCrop) || !resolved) {
278
+ return undefined;
279
+ }
280
+ return (0, cropBounds_1.calculateCropBounds)(resolved, props.dynamicCrop, width, height);
281
+ }, [elements, props.dynamicCrop, width, height]);
271
282
  // Compute effective colors based on theme
272
283
  const effectiveHomeIndicatorColor = isLightMode ? '#000000' : homeIndicatorColor;
273
284
  const effectiveStatusBarColor = isLightMode ? '#000000' : '#ffffff';
274
285
  const headerIconFilter = isLightMode ? 'invert(1)' : 'none';
275
- return ((0, jsx_runtime_1.jsxs)(DmComposition_1.BaseDmComposition, { width: width, height: height, backgroundColor: colors.background, showDebugOverlay: showDebugOverlay, referenceImageUrl: referenceImageUrl, showReferenceImage: showReferenceImage, referenceOpacity: referenceOpacity, currentProps: { ...props },
286
+ return ((0, jsx_runtime_1.jsxs)(DmComposition_1.BaseDmComposition, { width: width, height: height, cropBounds: cropBounds, backgroundColor: colors.background, showDebugOverlay: showDebugOverlay, referenceImageUrl: referenceImageUrl, showReferenceImage: showReferenceImage, referenceOpacity: referenceOpacity, currentProps: { ...props },
276
287
  // Status bar props
277
288
  time: time, timeRight: timeRight, timeBottom: timeBottom, timeFontSize: timeFontSize, timeLetterSpacing: timeLetterSpacing, noNotisTop: noNotisTop, noNotisBottom: noNotisBottom, noNotisLeft: noNotisLeft, noNotisRight: noNotisRight, cellLevel: cellLevel, cell1Top: cell1Top, cell1Bottom: cell1Bottom, cell1Left: cell1Left, cell1Right: cell1Right, cell2Top: cell2Top, cell2Bottom: cell2Bottom, cell2Left: cell2Left, cell2Right: cell2Right, cell3Top: cell3Top, cell3Bottom: cell3Bottom, cell3Left: cell3Left, cell3Right: cell3Right, cell4Top: cell4Top, cell4Bottom: cell4Bottom, cell4Left: cell4Left, cell4Right: cell4Right, wifiTop: wifiTop, wifiBottom: wifiBottom, wifiLeft: wifiLeft, wifiRight: wifiRight, batteryTop: batteryTop, batteryBottom: batteryBottom, batteryLeft: batteryLeft, batteryRight: batteryRight,
278
289
  // Home indicator props
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Minimal types for the stripped-down composition.
5
5
  */
6
+ import type { DynamicCropConfig } from '../../types/crop';
6
7
  /** Instagram theme - light or dark mode */
7
8
  export type IgTheme = 'light' | 'dark';
8
9
  /** Message sender type */
@@ -212,4 +213,6 @@ export interface InstagramDmCompositionProps {
212
213
  dividerLineY?: number;
213
214
  dividerLineColor?: string;
214
215
  headerBackgroundColor?: string;
216
+ /** Dynamic cropping configuration - crop output to element bounds */
217
+ dynamicCrop?: DynamicCropConfig;
215
218
  }
@@ -4,6 +4,7 @@
4
4
  * These are the types exposed to users via the automation node.
5
5
  * They provide a simpler interface than the full InstagramDmCompositionProps.
6
6
  */
7
+ import type { DynamicCropConfig } from './crop';
7
8
  /** Message sender side */
8
9
  export type IgDmSender = 'user' | 'recipient';
9
10
  /** Theme for the DM composition */
@@ -57,4 +58,6 @@ export interface InstagramDmPublicInput {
57
58
  * Required if any message has isStoryReply=true.
58
59
  */
59
60
  storyImageUrl?: string;
61
+ /** Dynamic cropping configuration - crop output to DM element bounds */
62
+ dynamicCrop?: DynamicCropConfig;
60
63
  }
package/dist/render.d.ts CHANGED
@@ -100,6 +100,8 @@ export interface SubmitInstagramDmRenderJobParams {
100
100
  messages: CreateDmMessage[];
101
101
  /** Image attachment URL (used for story reply) */
102
102
  imageAttachmentUrl?: string;
103
+ /** Dynamic crop configuration - crop output to DM element bounds */
104
+ dynamicCrop?: import('./render/types/crop').DynamicCropConfig;
103
105
  }
104
106
  interface ImDmMessageInternal {
105
107
  id: string;
@@ -132,6 +134,8 @@ export interface SubmitIMessageDmRenderJobParams {
132
134
  unreadBadgeText?: string;
133
135
  /** Message header timestamp text (e.g., "Today 2:15 PM") */
134
136
  messageHeaderTimestampText?: string;
137
+ /** Dynamic crop configuration - crop output to DM element bounds */
138
+ dynamicCrop?: import('./render/types/crop').DynamicCropConfig;
135
139
  }
136
140
  /**
137
141
  * Submit an image render job to the Modal renderer.
package/dist/render.js CHANGED
@@ -314,6 +314,7 @@ async function submitInstagramDmRenderJob(params) {
314
314
  theme: params.theme,
315
315
  messages: transformedMessages,
316
316
  storyImageUrl: hasStoryReply ? params.imageAttachmentUrl : undefined,
317
+ dynamicCrop: params.dynamicCrop,
317
318
  },
318
319
  },
319
320
  output_type: 'image',
@@ -362,6 +363,7 @@ async function submitIMessageDmRenderJob(params) {
362
363
  readReceiptText: params.readReceiptText,
363
364
  unreadBadgeText: params.unreadBadgeText,
364
365
  messageHeaderTimestampText: params.messageHeaderTimestampText,
366
+ dynamicCrop: params.dynamicCrop,
365
367
  },
366
368
  },
367
369
  output_type: 'image',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugcinc",
3
- "version": "4.5.43",
3
+ "version": "4.5.44",
4
4
  "description": "TypeScript/JavaScript client for the UGC Inc API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",