ugcinc 4.7.0 → 4.8.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/README.md
CHANGED
|
@@ -93,6 +93,22 @@ do {
|
|
|
93
93
|
} while (cursor);
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
## Caption Overlays
|
|
97
|
+
|
|
98
|
+
Video posts can carry text overlays to display on the video. Pass `captionOverlays` to
|
|
99
|
+
`posts.createVideo()`; each `CaptionOverlay` is `{ text, x, y, fontSize }`, where `x`/`y` are the
|
|
100
|
+
overlay center as a fraction (0-1) of the video width/height and `fontSize` is a fraction (0-1) of
|
|
101
|
+
the video height. Overlays are returned on the `Post` as `caption_overlays`.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
await client.posts.createVideo({
|
|
105
|
+
accountId: "acc_123",
|
|
106
|
+
videoUrl: "https://example.com/video.mp4",
|
|
107
|
+
caption: "Post description",
|
|
108
|
+
captionOverlays: [{ text: "Wait for it...", x: 0.5, y: 0.2, fontSize: 0.04 }],
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
96
112
|
## Useful Exports
|
|
97
113
|
|
|
98
114
|
- `UGCClient` for API access
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type { InputType } from './automations/nodes/types';
|
|
|
27
27
|
export type { ClientConfig } from './base';
|
|
28
28
|
export type { Account, AccountStat, AccountTask, EditProfileInfo, GetAccountsParams, GetAccountStatsParams, GetAccountStatusParams, AccountInfoUpdate, UpdateAccountInfoParams, AccountInfoUpdateResult, UpdateAccountInfoResponse, AccountSocialUpdate, UpdateAccountSocialParams, AccountSocialUpdateResult, UpdateAccountSocialResponse, DeleteAccountPostsParams, DeleteAccountPostsResponse, ResetWarmupParams, ResetWarmupResponse, NicheSwitchUpdate, NicheSwitchParams, NicheSwitchResult, NicheSwitchResponse, CreateAccountInput, CreateAccountsParams, CreateAccountResult, CreateAccountsResponse, TroubleshootFailReason, TroubleshootAccount, TroubleshootParams, } from './accounts';
|
|
29
29
|
export type { TaskType, Task, GetTasksParams } from './tasks';
|
|
30
|
-
export type { PostType, PostStatus, Post, PostStat, GetPostsParams, CreateDraftParams, CreateSlideshowParams, GetPostStatsParams, GetPostStatusParams, CreateVideoParams, UpdatePostParams, DeletePostsParams, DeletePostsResponse, RetryPostsParams, SetPostStatusParams, SetPostStatusResponse, PreviewScheduleEntry, PreviewScheduleParams, PreviewScheduleResult, } from './posts';
|
|
30
|
+
export type { PostType, PostStatus, Post, PostStat, CaptionOverlay, GetPostsParams, CreateDraftParams, CreateSlideshowParams, GetPostStatsParams, GetPostStatusParams, CreateVideoParams, UpdatePostParams, DeletePostsParams, DeletePostsResponse, RetryPostsParams, SetPostStatusParams, SetPostStatusResponse, PreviewScheduleEntry, PreviewScheduleParams, PreviewScheduleResult, } from './posts';
|
|
31
31
|
export type { RefreshStatsParams, RefreshStatsError, RefreshStatsResponse, RefreshStatsProgressResponse, RefreshStartEvent, RefreshProgressEvent, RefreshDoneEvent, RefreshStreamEvent, DailyAggregatedStat, GetDailyAggregatedStatsParams, DailyAccountStat, GetDailyAccountStatsParams, DailyPostStat, GetDailyPostStatsParams, DashboardDailyStat, GetDashboardDailyStatsParams, TopAccount, GetTopAccountsParams, TopPost, GetTopPostsParams, } from './stats';
|
|
32
32
|
export type { Org, ApiKey, DeleteApiKeyParams, EditApiKeyParams, IntegrationKey, IntegrationProvider, UpsertIntegrationKeyParams, DeleteIntegrationKeyParams } from './org';
|
|
33
33
|
export type { UserMedia, MediaUse, SocialAudio, Media, GetMediaParams, GetSocialAudioParams, UploadMediaParams, UploadMediaResponse, MediaTagUpdate, UpdateMediaTagsParams, MediaTagUpdateResult, UpdateMediaTagsResponse, UpdateMediaTagParams, UpdateMediaNameParams, DeleteMediaParams, DeleteMediaResponse, CreateSocialAudioParams, ImportTextParams, ImportTextResponse, CreateMediaFromUrlParams, GetMediaUseParams, GetMediaUseResponse, FilterMediaParams, FilterMediaResponse, GetUploadTokenParams, UploadTokenResponse, } from './media';
|
package/dist/posts.d.ts
CHANGED
|
@@ -2,6 +2,17 @@ import { BaseClient } from './base';
|
|
|
2
2
|
import type { ApiResponse } from './types';
|
|
3
3
|
export type PostType = 'video' | 'slideshow';
|
|
4
4
|
export type PostStatus = 'draft' | 'scheduled' | 'pending' | 'complete' | 'failed' | 'retrying' | 'deleting' | 'deleted' | 'hidden' | 'require-approval';
|
|
5
|
+
/**
|
|
6
|
+
* A text overlay to display on top of a video post.
|
|
7
|
+
* x/y are the overlay center as a fraction (0-1) of the video width/height;
|
|
8
|
+
* fontSize is a fraction (0-1) of the video height.
|
|
9
|
+
*/
|
|
10
|
+
export interface CaptionOverlay {
|
|
11
|
+
text: string;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
fontSize: number;
|
|
15
|
+
}
|
|
5
16
|
export interface Post {
|
|
6
17
|
id: string;
|
|
7
18
|
account_id: string | null;
|
|
@@ -9,6 +20,7 @@ export interface Post {
|
|
|
9
20
|
status: PostStatus;
|
|
10
21
|
social_id: string | null;
|
|
11
22
|
caption: string | null;
|
|
23
|
+
caption_overlays: CaptionOverlay[] | null;
|
|
12
24
|
title: string | null;
|
|
13
25
|
media_urls: string[] | null;
|
|
14
26
|
thumbnail_url: string | null;
|
|
@@ -62,6 +74,7 @@ export interface GetPostStatusParams {
|
|
|
62
74
|
export interface CreateVideoParams {
|
|
63
75
|
accountId: string | null;
|
|
64
76
|
caption?: string;
|
|
77
|
+
captionOverlays?: CaptionOverlay[];
|
|
65
78
|
socialAudioId?: string;
|
|
66
79
|
postTime?: string;
|
|
67
80
|
videoUrl: string;
|
|
@@ -12,6 +12,14 @@ const react_1 = require("react");
|
|
|
12
12
|
const remotion_1 = require("remotion");
|
|
13
13
|
const defaults_1 = require("../utils/defaults");
|
|
14
14
|
const text_1 = require("../utils/text");
|
|
15
|
+
/**
|
|
16
|
+
* Preview sync threshold: remotion hard-seeks the media tag when it drifts
|
|
17
|
+
* further than this from the timeline (default ~0.65s). Fractional playback
|
|
18
|
+
* speeds accumulate drift, so the default produces large visible "rubberband"
|
|
19
|
+
* jumps; a tight threshold keeps corrections to a few frames. Preview-only —
|
|
20
|
+
* ignored during server rendering.
|
|
21
|
+
*/
|
|
22
|
+
const ACCEPTABLE_TIME_SHIFT_SECONDS = 0.2;
|
|
15
23
|
/**
|
|
16
24
|
* Map FitMode to CSS object-fit
|
|
17
25
|
*/
|
|
@@ -102,7 +110,7 @@ function VideoElement({ segment, src, startFrame, durationInFrames, scale = 1 })
|
|
|
102
110
|
const effectiveDurationMs = loopSourceDurationMs / speed;
|
|
103
111
|
return Math.max(1, Math.round((effectiveDurationMs / 1000) * fps));
|
|
104
112
|
}, [loop, loopSourceDurationMs, speed, fps]);
|
|
105
|
-
const videoElement = ((0, jsx_runtime_1.jsx)(remotion_1.OffthreadVideo, { src: src, style: videoStyle, startFrom: startFromFrames, playbackRate: speed, volume: volume }));
|
|
113
|
+
const videoElement = ((0, jsx_runtime_1.jsx)(remotion_1.OffthreadVideo, { src: src, style: videoStyle, startFrom: startFromFrames, playbackRate: speed, volume: volume, acceptableTimeShiftInSeconds: ACCEPTABLE_TIME_SHIFT_SECONDS }));
|
|
106
114
|
return ((0, jsx_runtime_1.jsx)("div", { style: containerStyle, children: loop && loopDurationInFrames ? ((0, jsx_runtime_1.jsx)(remotion_1.Loop, { durationInFrames: loopDurationInFrames, children: videoElement })) : (videoElement) }));
|
|
107
115
|
}
|
|
108
116
|
exports.default = VideoElement;
|
|
@@ -134,27 +134,28 @@ function calculateSegmentTimings(config, fps) {
|
|
|
134
134
|
* ```
|
|
135
135
|
*/
|
|
136
136
|
function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
|
|
137
|
-
const
|
|
138
|
-
const { fps, durationInFrames } = (0, remotion_1.useVideoConfig)();
|
|
137
|
+
const { fps } = (0, remotion_1.useVideoConfig)();
|
|
139
138
|
// Calculate segment timings
|
|
140
139
|
const segmentTimings = (0, react_1.useMemo)(() => calculateSegmentTimings(config, fps), [config, fps]);
|
|
141
|
-
//
|
|
142
|
-
|
|
140
|
+
// All visual segments, statically sorted by zIndex (stable sort keeps the
|
|
141
|
+
// original channel order for ties). Every segment is always rendered inside
|
|
142
|
+
// a <Sequence> window, which handles per-frame visibility — mounting is no
|
|
143
|
+
// longer gated on the current frame, so upcoming media can premount.
|
|
144
|
+
const visualTimings = (0, react_1.useMemo)(() => {
|
|
143
145
|
return segmentTimings
|
|
144
|
-
.filter(({ segment
|
|
145
|
-
|
|
146
|
-
if (segment.type !== 'video' && segment.type !== 'image' && segment.type !== 'text' && segment.type !== 'image-sequence' && segment.type !== 'video-sequence') {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
// Check if active at current frame
|
|
150
|
-
return frame >= startFrame && frame < endFrame;
|
|
146
|
+
.filter(({ segment }) => {
|
|
147
|
+
return segment.type === 'video' || segment.type === 'image' || segment.type === 'text' || segment.type === 'image-sequence' || segment.type === 'video-sequence';
|
|
151
148
|
})
|
|
152
149
|
.sort((a, b) => {
|
|
153
150
|
const aZ = a.segment.zIndex ?? 0;
|
|
154
151
|
const bZ = b.segment.zIndex ?? 0;
|
|
155
152
|
return aZ - bZ;
|
|
156
153
|
});
|
|
157
|
-
}, [segmentTimings
|
|
154
|
+
}, [segmentTimings]);
|
|
155
|
+
// Premount window for video sequences: in the Player, upcoming videos
|
|
156
|
+
// mount invisibly this many frames early so they buffer and pre-seek
|
|
157
|
+
// before becoming visible. No effect on server rendering.
|
|
158
|
+
const premountFrames = Math.round(fps * 2);
|
|
158
159
|
// Get source URL for a segment
|
|
159
160
|
const getSource = (segment) => {
|
|
160
161
|
if (segment.source) {
|
|
@@ -170,25 +171,25 @@ function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
|
|
|
170
171
|
const audioTimings = (0, react_1.useMemo)(() => {
|
|
171
172
|
return segmentTimings.filter(({ segment }) => segment.type === 'audio');
|
|
172
173
|
}, [segmentTimings]);
|
|
173
|
-
return ((0, jsx_runtime_1.jsxs)(remotion_1.AbsoluteFill, { style: { backgroundColor: '#000000' }, children: [
|
|
174
|
+
return ((0, jsx_runtime_1.jsxs)(remotion_1.AbsoluteFill, { style: { backgroundColor: '#000000' }, children: [visualTimings.map(({ segment, startFrame, durationInFrames }) => {
|
|
174
175
|
if (segment.type === 'text') {
|
|
175
176
|
const textSegment = segment;
|
|
176
177
|
const text = getTextContent(textSegment);
|
|
177
|
-
return ((0, jsx_runtime_1.jsx)(TextElement_1.TextElement, { segment: { ...textSegment, text }, scale: 1 }, segment.id));
|
|
178
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, children: (0, jsx_runtime_1.jsx)(TextElement_1.TextElement, { segment: { ...textSegment, text }, scale: 1 }) }, segment.id));
|
|
178
179
|
}
|
|
179
180
|
if (segment.type === 'image') {
|
|
180
181
|
const src = getSource(segment);
|
|
181
182
|
if (!src) {
|
|
182
183
|
return null;
|
|
183
184
|
}
|
|
184
|
-
return ((0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: segment, src: src, startFrame:
|
|
185
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, children: (0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: segment, src: src, startFrame: 0, scale: 1 }) }, segment.id));
|
|
185
186
|
}
|
|
186
187
|
if (segment.type === 'video') {
|
|
187
188
|
const src = getSource(segment);
|
|
188
189
|
if (!src) {
|
|
189
190
|
return null;
|
|
190
191
|
}
|
|
191
|
-
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: segment, src: src, startFrame: 0, durationInFrames: durationInFrames, scale: 1 }) }, segment.id));
|
|
192
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame, durationInFrames: durationInFrames, premountFor: premountFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: segment, src: src, startFrame: 0, durationInFrames: durationInFrames, scale: 1 }) }, segment.id));
|
|
192
193
|
}
|
|
193
194
|
if (segment.type === 'image-sequence') {
|
|
194
195
|
const sequenceSegment = segment;
|
|
@@ -196,32 +197,29 @@ function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
|
|
|
196
197
|
if (items.length === 0) {
|
|
197
198
|
return null;
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
fadeIn: sequenceSegment.fadeIn,
|
|
223
|
-
};
|
|
224
|
-
return ((0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: syntheticImageSegment, src: currentItemUrl, startFrame: itemStartFrame, scale: 1 }, `${segment.id}-${currentItemUrl}`));
|
|
200
|
+
const itemDurationFrames = Math.max(1, Math.round((sequenceSegment.defaultDuration / 1000) * fps));
|
|
201
|
+
return items.map((itemUrl, itemIndex) => {
|
|
202
|
+
if (!itemUrl) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const syntheticImageSegment = {
|
|
206
|
+
id: `${segment.id}-item-${itemIndex}`,
|
|
207
|
+
type: 'image',
|
|
208
|
+
source: itemUrl,
|
|
209
|
+
order: segment.order,
|
|
210
|
+
offset: { type: 'absolute', value: 0 },
|
|
211
|
+
xOffset: sequenceSegment.xOffset,
|
|
212
|
+
yOffset: sequenceSegment.yOffset,
|
|
213
|
+
width: sequenceSegment.width,
|
|
214
|
+
height: sequenceSegment.height,
|
|
215
|
+
fit: sequenceSegment.fit,
|
|
216
|
+
rotation: sequenceSegment.rotation,
|
|
217
|
+
opacity: sequenceSegment.opacity,
|
|
218
|
+
zIndex: sequenceSegment.zIndex,
|
|
219
|
+
fadeIn: sequenceSegment.fadeIn,
|
|
220
|
+
};
|
|
221
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: startFrame + itemIndex * itemDurationFrames, durationInFrames: itemDurationFrames, children: (0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: syntheticImageSegment, src: itemUrl, startFrame: 0, scale: 1 }) }, `${segment.id}-item-${itemIndex}`));
|
|
222
|
+
});
|
|
225
223
|
}
|
|
226
224
|
if (segment.type === 'video-sequence') {
|
|
227
225
|
const sequenceSegment = segment;
|
|
@@ -230,44 +228,33 @@ function VideoEditorComposition({ config, sources = {}, textContent = {}, }) {
|
|
|
230
228
|
if (items.length === 0) {
|
|
231
229
|
return null;
|
|
232
230
|
}
|
|
233
|
-
// Calculate which item should be shown at current frame
|
|
234
|
-
const frameIntoSegment = frame - startFrame;
|
|
235
231
|
let accumulatedFrames = 0;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
const itemDurationFrames = Math.round((itemDurationMs / 1000) * fps);
|
|
241
|
-
if (frameIntoSegment < accumulatedFrames + itemDurationFrames) {
|
|
242
|
-
currentItemIndex = i;
|
|
243
|
-
itemStartFrame = startFrame + accumulatedFrames;
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
232
|
+
return items.map((itemUrl, itemIndex) => {
|
|
233
|
+
const itemDurationMs = durations[itemIndex] ?? 5000;
|
|
234
|
+
const itemDurationFrames = Math.max(1, Math.round((itemDurationMs / 1000) * fps));
|
|
235
|
+
const itemStartFrame = startFrame + accumulatedFrames;
|
|
246
236
|
accumulatedFrames += itemDurationFrames;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
fadeIn: sequenceSegment.fadeIn,
|
|
269
|
-
};
|
|
270
|
-
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: itemStartFrame, durationInFrames: itemDurationFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: syntheticVideoSegment, src: currentItemUrl, startFrame: 0, durationInFrames: itemDurationFrames, scale: 1 }) }, `${segment.id}-${currentItemUrl}`));
|
|
237
|
+
if (!itemUrl) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const syntheticVideoSegment = {
|
|
241
|
+
id: `${segment.id}-item-${itemIndex}`,
|
|
242
|
+
type: 'video',
|
|
243
|
+
source: itemUrl,
|
|
244
|
+
order: segment.order,
|
|
245
|
+
offset: { type: 'absolute', value: 0 },
|
|
246
|
+
xOffset: sequenceSegment.xOffset,
|
|
247
|
+
yOffset: sequenceSegment.yOffset,
|
|
248
|
+
width: sequenceSegment.width,
|
|
249
|
+
height: sequenceSegment.height,
|
|
250
|
+
fit: sequenceSegment.fit,
|
|
251
|
+
rotation: sequenceSegment.rotation,
|
|
252
|
+
opacity: sequenceSegment.opacity,
|
|
253
|
+
zIndex: sequenceSegment.zIndex,
|
|
254
|
+
fadeIn: sequenceSegment.fadeIn,
|
|
255
|
+
};
|
|
256
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.Sequence, { from: itemStartFrame, durationInFrames: itemDurationFrames, premountFor: premountFrames, children: (0, jsx_runtime_1.jsx)(VideoElement_1.VideoElement, { segment: syntheticVideoSegment, src: itemUrl, startFrame: 0, durationInFrames: itemDurationFrames, scale: 1 }) }, `${segment.id}-item-${itemIndex}`));
|
|
257
|
+
});
|
|
271
258
|
}
|
|
272
259
|
return null;
|
|
273
260
|
}), audioTimings.map(({ segment, startFrame, durationInFrames }) => {
|