ugcinc 4.6.3 → 4.7.1
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 +20 -1
- package/dist/accounts.d.ts +7 -0
- package/dist/accounts.js +3 -0
- package/dist/base.js +1 -0
- package/dist/posts.d.ts +7 -0
- package/dist/posts.js +3 -0
- package/dist/render/components/VideoElement.js +9 -1
- package/dist/render/compositions/VideoEditorComposition.js +65 -78
- package/dist/tools/accounts.js +2 -0
- package/dist/tools/posts.js +2 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ All client methods return the same response envelope:
|
|
|
58
58
|
|
|
59
59
|
```typescript
|
|
60
60
|
type ApiResponse<T> =
|
|
61
|
-
| { ok: true; code: 200; message: string; data: T }
|
|
61
|
+
| { ok: true; code: 200; message: string; data: T; nextCursor?: string | null }
|
|
62
62
|
| { ok: false; code: number; message: string };
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -74,6 +74,25 @@ if (posts.ok) {
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## Pagination
|
|
78
|
+
|
|
79
|
+
`accounts.getAccounts()` and `posts.getPosts()` support keyset pagination via `limit`/`cursor`.
|
|
80
|
+
Omit `limit` to fetch everything matching your filters (the default, unpaginated behavior); pass
|
|
81
|
+
`limit` to page through results newest-first, using each response's `nextCursor` to fetch the next
|
|
82
|
+
page (`null`/absent means there are no more pages):
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
let cursor: string | undefined;
|
|
86
|
+
const allPosts = [];
|
|
87
|
+
|
|
88
|
+
do {
|
|
89
|
+
const res = await client.posts.getPosts({ limit: 100, cursor });
|
|
90
|
+
if (!res.ok) break;
|
|
91
|
+
allPosts.push(...res.data);
|
|
92
|
+
cursor = res.nextCursor ?? undefined;
|
|
93
|
+
} while (cursor);
|
|
94
|
+
```
|
|
95
|
+
|
|
77
96
|
## Useful Exports
|
|
78
97
|
|
|
79
98
|
- `UGCClient` for API access
|
package/dist/accounts.d.ts
CHANGED
|
@@ -74,6 +74,10 @@ export interface GetAccountsParams {
|
|
|
74
74
|
org_group?: string;
|
|
75
75
|
user_group?: string;
|
|
76
76
|
status?: 'uninitialized' | 'pending' | 'initialized' | 'setup' | 'warming' | 'warmed' | 'needs_replacement' | 'replacing' | 'failed' | 'deleted' | 'reclaimed';
|
|
77
|
+
/** Max rows to return. Omit to fetch all matching accounts (no pagination). */
|
|
78
|
+
limit?: number;
|
|
79
|
+
/** Opaque cursor from a previous response's `nextCursor`, to fetch the next page. */
|
|
80
|
+
cursor?: string;
|
|
77
81
|
}
|
|
78
82
|
export interface GetAccountStatsParams {
|
|
79
83
|
accountIds?: string[];
|
|
@@ -239,6 +243,9 @@ export interface TroubleshootParams {
|
|
|
239
243
|
export declare class AccountsClient extends BaseClient {
|
|
240
244
|
/**
|
|
241
245
|
* Get accounts with optional filters
|
|
246
|
+
*
|
|
247
|
+
* Pass `limit` to paginate; the response's `nextCursor` (when present) can be
|
|
248
|
+
* passed back as `cursor` to fetch the next page, ordered newest-first.
|
|
242
249
|
*/
|
|
243
250
|
getAccounts(params?: GetAccountsParams): Promise<ApiResponse<Account[]>>;
|
|
244
251
|
/**
|
package/dist/accounts.js
CHANGED
|
@@ -8,6 +8,9 @@ const base_1 = require("./base");
|
|
|
8
8
|
class AccountsClient extends base_1.BaseClient {
|
|
9
9
|
/**
|
|
10
10
|
* Get accounts with optional filters
|
|
11
|
+
*
|
|
12
|
+
* Pass `limit` to paginate; the response's `nextCursor` (when present) can be
|
|
13
|
+
* passed back as `cursor` to fetch the next page, ordered newest-first.
|
|
11
14
|
*/
|
|
12
15
|
async getAccounts(params) {
|
|
13
16
|
return this.post('/accounts', params ?? {});
|
package/dist/base.js
CHANGED
package/dist/posts.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export interface GetPostsParams {
|
|
|
32
32
|
startDate?: string;
|
|
33
33
|
endDate?: string;
|
|
34
34
|
includeHidden?: boolean;
|
|
35
|
+
/** Max rows to return. Omit to fetch all matching posts (no pagination). */
|
|
36
|
+
limit?: number;
|
|
37
|
+
/** Opaque cursor from a previous response's `nextCursor`, to fetch the next page. */
|
|
38
|
+
cursor?: string;
|
|
35
39
|
}
|
|
36
40
|
export interface CreateSlideshowParams {
|
|
37
41
|
accountId: string | null;
|
|
@@ -131,6 +135,9 @@ export interface PreviewScheduleResult {
|
|
|
131
135
|
export declare class PostsClient extends BaseClient {
|
|
132
136
|
/**
|
|
133
137
|
* Get posts with optional filters
|
|
138
|
+
*
|
|
139
|
+
* Pass `limit` to paginate; the response's `nextCursor` (when present) can be
|
|
140
|
+
* passed back as `cursor` to fetch the next page, ordered newest-first.
|
|
134
141
|
*/
|
|
135
142
|
getPosts(params?: GetPostsParams): Promise<ApiResponse<Post[]>>;
|
|
136
143
|
/**
|
package/dist/posts.js
CHANGED
|
@@ -8,6 +8,9 @@ const base_1 = require("./base");
|
|
|
8
8
|
class PostsClient extends base_1.BaseClient {
|
|
9
9
|
/**
|
|
10
10
|
* Get posts with optional filters
|
|
11
|
+
*
|
|
12
|
+
* Pass `limit` to paginate; the response's `nextCursor` (when present) can be
|
|
13
|
+
* passed back as `cursor` to fetch the next page, ordered newest-first.
|
|
11
14
|
*/
|
|
12
15
|
async getPosts(params) {
|
|
13
16
|
return this.post('/post', params ?? {});
|
|
@@ -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 }) => {
|
package/dist/tools/accounts.js
CHANGED
|
@@ -11,6 +11,8 @@ exports.accountTools = [
|
|
|
11
11
|
org_group: zod_1.z.string().optional().describe('Filter by org group'),
|
|
12
12
|
user_group: zod_1.z.string().optional().describe('Filter by user group'),
|
|
13
13
|
status: zod_1.z.enum(['uninitialized', 'pending', 'initialized', 'setup', 'warming', 'warmed', 'needs_replacement', 'replacing', 'failed', 'deleted']).optional().describe('Filter by account status'),
|
|
14
|
+
limit: zod_1.z.number().optional().describe('Max rows to return, for pagination'),
|
|
15
|
+
cursor: zod_1.z.string().optional().describe('Opaque cursor from a previous response\'s nextCursor, to fetch the next page'),
|
|
14
16
|
}).optional().describe('Optional filters'),
|
|
15
17
|
execute: async (client, params) => {
|
|
16
18
|
return client.accounts.getAccounts(params ?? undefined);
|
package/dist/tools/posts.js
CHANGED
|
@@ -11,6 +11,8 @@ exports.postTools = [
|
|
|
11
11
|
postIds: zod_1.z.array(zod_1.z.string()).optional().describe('Filter by specific post IDs'),
|
|
12
12
|
startDate: zod_1.z.string().optional().describe('Start date (ISO 8601)'),
|
|
13
13
|
endDate: zod_1.z.string().optional().describe('End date (ISO 8601)'),
|
|
14
|
+
limit: zod_1.z.number().optional().describe('Max rows to return, for pagination'),
|
|
15
|
+
cursor: zod_1.z.string().optional().describe('Opaque cursor from a previous response\'s nextCursor, to fetch the next page'),
|
|
14
16
|
}).optional(),
|
|
15
17
|
execute: async (client, params) => {
|
|
16
18
|
return client.posts.getPosts(params ?? undefined);
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface SuccessResponse<T> {
|
|
|
6
6
|
message: string;
|
|
7
7
|
data: T;
|
|
8
8
|
ok: true;
|
|
9
|
+
/** Present on keyset-paginated endpoints (see `limit`/`cursor` params). Pass back as `cursor` to fetch the next page; `null`/absent means no more pages. */
|
|
10
|
+
nextCursor?: string | null;
|
|
9
11
|
}
|
|
10
12
|
export interface ErrorResponse {
|
|
11
13
|
code: number;
|