@sogni-ai/sogni-client 4.0.0-alpha.5 → 4.0.0-alpha.50

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 (109) hide show
  1. package/CHANGELOG.md +345 -0
  2. package/README.md +295 -58
  3. package/dist/Account/index.d.ts +18 -16
  4. package/dist/Account/index.js +42 -21
  5. package/dist/Account/index.js.map +1 -1
  6. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.d.ts +66 -0
  7. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js +332 -0
  8. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js.map +1 -0
  9. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +28 -0
  10. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +203 -0
  11. package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -0
  12. package/dist/ApiClient/WebSocketClient/events.d.ts +12 -0
  13. package/dist/ApiClient/WebSocketClient/index.d.ts +2 -2
  14. package/dist/ApiClient/WebSocketClient/index.js +13 -3
  15. package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
  16. package/dist/ApiClient/WebSocketClient/types.d.ts +13 -0
  17. package/dist/ApiClient/index.d.ts +4 -4
  18. package/dist/ApiClient/index.js +23 -4
  19. package/dist/ApiClient/index.js.map +1 -1
  20. package/dist/Projects/Job.d.ts +44 -4
  21. package/dist/Projects/Job.js +83 -16
  22. package/dist/Projects/Job.js.map +1 -1
  23. package/dist/Projects/Project.d.ts +18 -0
  24. package/dist/Projects/Project.js +38 -10
  25. package/dist/Projects/Project.js.map +1 -1
  26. package/dist/Projects/createJobRequestMessage.d.ts +2 -1
  27. package/dist/Projects/createJobRequestMessage.js +173 -14
  28. package/dist/Projects/createJobRequestMessage.js.map +1 -1
  29. package/dist/Projects/index.d.ts +114 -11
  30. package/dist/Projects/index.js +504 -47
  31. package/dist/Projects/index.js.map +1 -1
  32. package/dist/Projects/types/ComfySamplerParams.d.ts +0 -0
  33. package/dist/Projects/types/ComfySamplerParams.js +2 -0
  34. package/dist/Projects/types/ComfySamplerParams.js.map +1 -0
  35. package/dist/Projects/types/EstimationResponse.d.ts +2 -0
  36. package/dist/Projects/types/ModelOptions.d.ts +31 -0
  37. package/dist/Projects/types/ModelOptions.js +56 -0
  38. package/dist/Projects/types/ModelOptions.js.map +1 -0
  39. package/dist/Projects/types/ModelTiersRaw.d.ts +67 -0
  40. package/dist/Projects/types/ModelTiersRaw.js +15 -0
  41. package/dist/Projects/types/ModelTiersRaw.js.map +1 -0
  42. package/dist/Projects/types/events.d.ts +5 -1
  43. package/dist/Projects/types/index.d.ts +201 -42
  44. package/dist/Projects/types/index.js +8 -0
  45. package/dist/Projects/types/index.js.map +1 -1
  46. package/dist/Projects/utils/index.d.ts +20 -0
  47. package/dist/Projects/utils/index.js +82 -0
  48. package/dist/Projects/utils/index.js.map +1 -0
  49. package/dist/Projects/utils/samplers.d.ts +6 -0
  50. package/dist/Projects/utils/samplers.js +39 -0
  51. package/dist/Projects/utils/samplers.js.map +1 -0
  52. package/dist/Projects/utils/scheduler.d.ts +6 -0
  53. package/dist/Projects/utils/scheduler.js +30 -0
  54. package/dist/Projects/utils/scheduler.js.map +1 -0
  55. package/dist/index.d.ts +11 -3
  56. package/dist/index.js +8 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/lib/AuthManager/TokenAuthManager.js +0 -2
  59. package/dist/lib/AuthManager/TokenAuthManager.js.map +1 -1
  60. package/dist/lib/DataEntity.js +4 -2
  61. package/dist/lib/DataEntity.js.map +1 -1
  62. package/dist/lib/RestClient.js +15 -2
  63. package/dist/lib/RestClient.js.map +1 -1
  64. package/dist/lib/{utils.js → utils/index.js} +1 -1
  65. package/dist/lib/utils/index.js.map +1 -0
  66. package/dist/lib/validation.d.ts +31 -2
  67. package/dist/lib/validation.js +80 -13
  68. package/dist/lib/validation.js.map +1 -1
  69. package/package.json +4 -4
  70. package/src/Account/index.ts +39 -20
  71. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.ts +426 -0
  72. package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +237 -0
  73. package/src/ApiClient/WebSocketClient/events.ts +14 -0
  74. package/src/ApiClient/WebSocketClient/index.ts +15 -5
  75. package/src/ApiClient/WebSocketClient/types.ts +16 -0
  76. package/src/ApiClient/index.ts +30 -8
  77. package/src/Projects/Job.ts +97 -16
  78. package/src/Projects/Project.ts +46 -13
  79. package/src/Projects/createJobRequestMessage.ts +234 -34
  80. package/src/Projects/index.ts +533 -51
  81. package/src/Projects/types/ComfySamplerParams.ts +0 -0
  82. package/src/Projects/types/EstimationResponse.ts +2 -0
  83. package/src/Projects/types/ModelOptions.ts +92 -0
  84. package/src/Projects/types/ModelTiersRaw.ts +86 -0
  85. package/src/Projects/types/events.ts +6 -0
  86. package/src/Projects/types/index.ts +235 -45
  87. package/src/Projects/utils/index.ts +77 -0
  88. package/src/Projects/utils/samplers.ts +36 -0
  89. package/src/Projects/utils/scheduler.ts +27 -0
  90. package/src/index.ts +36 -9
  91. package/src/lib/AuthManager/TokenAuthManager.ts +0 -2
  92. package/src/lib/DataEntity.ts +4 -2
  93. package/src/lib/RestClient.ts +16 -2
  94. package/src/lib/validation.ts +90 -17
  95. package/dist/Projects/types/SamplerParams.d.ts +0 -15
  96. package/dist/Projects/types/SamplerParams.js +0 -21
  97. package/dist/Projects/types/SamplerParams.js.map +0 -1
  98. package/dist/Projects/types/SchedulerParams.d.ts +0 -13
  99. package/dist/Projects/types/SchedulerParams.js +0 -19
  100. package/dist/Projects/types/SchedulerParams.js.map +0 -1
  101. package/dist/Projects/utils.d.ts +0 -2
  102. package/dist/Projects/utils.js +0 -14
  103. package/dist/Projects/utils.js.map +0 -1
  104. package/dist/lib/utils.js.map +0 -1
  105. package/src/Projects/types/SamplerParams.ts +0 -19
  106. package/src/Projects/types/SchedulerParams.ts +0 -17
  107. package/src/Projects/utils.ts +0 -12
  108. /package/dist/lib/{utils.d.ts → utils/index.d.ts} +0 -0
  109. /package/src/lib/{utils.ts → utils/index.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import Job, { JobData } from './Job';
2
2
  import DataEntity, { EntityEvents } from '../lib/DataEntity';
3
- import { ProjectParams } from './types';
3
+ import { isImageParams, ProjectParams } from './types';
4
4
  import cloneDeep from 'lodash/cloneDeep';
5
5
  import ErrorData from '../types/ErrorData';
6
6
  import getUUID from '../lib/getUUID';
@@ -8,8 +8,8 @@ import { RawJob, RawProject } from './types/RawProject';
8
8
  import ProjectsApi from './index';
9
9
  import { Logger } from '../lib/DefaultLogger';
10
10
 
11
- // If project is not finished and had no updates for 1 minute, force refresh
12
- const PROJECT_TIMEOUT = 60 * 1000;
11
+ // If project is not finished and had no updates for 2 minutes, force refresh
12
+ const PROJECT_TIMEOUT = 2 * 60 * 1000;
13
13
  const MAX_FAILED_SYNC_ATTEMPTS = 3;
14
14
 
15
15
  export type ProjectStatus =
@@ -39,6 +39,11 @@ export interface ProjectData {
39
39
  params: ProjectParams;
40
40
  queuePosition: number;
41
41
  status: ProjectStatus;
42
+ /**
43
+ * Estimated completion time of the project (for long-running projects like video generation).
44
+ * Is equal to maximum job ETA
45
+ */
46
+ eta?: Date;
42
47
  error?: ErrorData;
43
48
  }
44
49
  /** @inline */
@@ -93,10 +98,22 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
93
98
  return this.data.params;
94
99
  }
95
100
 
101
+ get type() {
102
+ return this.params.type;
103
+ }
104
+
96
105
  get status() {
97
106
  return this.data.status;
98
107
  }
99
108
 
109
+ /**
110
+ * Estimated time of completion in seconds (for long-running projects like video generation).
111
+ * Updated by ComfyUI workers during inference.
112
+ */
113
+ get eta() {
114
+ return this.data.eta;
115
+ }
116
+
100
117
  get finished() {
101
118
  return ['completed', 'failed', 'canceled'].includes(this.status);
102
119
  }
@@ -110,10 +127,10 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
110
127
  */
111
128
  get progress() {
112
129
  // Worker can reduce the number of steps in the job, so we need to calculate the progress based on the actual number of steps
113
- const stepsPerJob = this.jobs.length ? this.jobs[0].stepCount : this.data.params.steps;
114
- const jobCount = this.data.params.numberOfImages;
130
+ const stepsPerJob = this.jobs.length ? this.jobs[0].stepCount : (this.data.params.steps ?? 0);
131
+ const jobCount = this.data.params.numberOfMedia;
115
132
  const stepsDone = this._jobs.reduce((acc, job) => acc + job.step, 0);
116
- return Math.round((stepsDone / (stepsPerJob * jobCount)) * 100);
133
+ return Math.round((stepsDone / ((stepsPerJob ?? 1) * jobCount)) * 100);
117
134
  }
118
135
 
119
136
  get queuePosition() {
@@ -156,10 +173,10 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
156
173
  }
157
174
 
158
175
  return new Promise((resolve, reject) => {
159
- this.on('completed', (images) => {
176
+ this.once('completed', (images) => {
160
177
  resolve(images);
161
178
  });
162
- this.on('failed', (error) => {
179
+ this.once('failed', (error) => {
163
180
  reject(error);
164
181
  });
165
182
  });
@@ -192,7 +209,7 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
192
209
  this._timeout = null;
193
210
  }
194
211
  if (keys.includes('status') || keys.includes('jobs')) {
195
- const allJobsStarted = this.jobs.length >= this.params.numberOfImages;
212
+ const allJobsStarted = this.jobs.length >= this.params.numberOfMedia;
196
213
  const allJobsDone = this.jobs.every((job) => job.finished);
197
214
  if (this.data.status === 'completed' && allJobsStarted && allJobsDone) {
198
215
  return this.emit('completed', this.resultUrls);
@@ -203,6 +220,16 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
203
220
  }
204
221
  }
205
222
 
223
+ /**
224
+ * Refresh the lastUpdated timestamp to prevent timeout.
225
+ * Used when receiving socket events that indicate the project is still active
226
+ * (e.g., jobETA events during long-running video generation).
227
+ * @internal
228
+ */
229
+ _keepAlive() {
230
+ this.lastUpdated = new Date();
231
+ }
232
+
206
233
  /**
207
234
  * This is internal method to add a job to the project. Do not call this directly.
208
235
  * @internal
@@ -231,7 +258,11 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
231
258
  private _checkForTimeout() {
232
259
  if (this.lastUpdated.getTime() + PROJECT_TIMEOUT < Date.now()) {
233
260
  this._syncToServer().catch((error) => {
234
- this._logger.error(error);
261
+ // 404 errors are expected when project is still initializing and not yet available via REST API
262
+ // Only log non-404 errors to avoid confusing users
263
+ if (error.status !== 404) {
264
+ this._logger.error(error);
265
+ }
235
266
  this._failedSyncAttempts++;
236
267
  if (this._failedSyncAttempts >= MAX_FAILED_SYNC_ATTEMPTS) {
237
268
  this._logger.error(
@@ -298,11 +329,13 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
298
329
  const delta: Partial<ProjectData> = {
299
330
  params: {
300
331
  ...this.data.params,
301
- numberOfImages: data.imageCount,
302
- steps: data.stepCount,
303
- numberOfPreviews: data.previewCount
332
+ numberOfMedia: data.imageCount,
333
+ steps: data.stepCount
304
334
  }
305
335
  };
336
+ if (delta.params && isImageParams(delta.params)) {
337
+ delta.params.numberOfPreviews = data.previewCount;
338
+ }
306
339
  if (PROJECT_STATUS_MAP[data.status]) {
307
340
  delta.status = PROJECT_STATUS_MAP[data.status];
308
341
  }
@@ -1,11 +1,70 @@
1
- import { ProjectParams } from './types';
1
+ import {
2
+ ImageProjectParams,
3
+ isImageParams,
4
+ isVideoParams,
5
+ ProjectParams,
6
+ VideoProjectParams
7
+ } from './types';
2
8
  import { ControlNetParams, ControlNetParamsRaw } from './types/ControlNetParams';
3
9
  import {
4
10
  validateNumber,
5
11
  validateCustomImageSize,
12
+ validateVideoSize,
13
+ validateTeacacheThreshold,
14
+ isComfyModel,
15
+ validateVideoDuration,
6
16
  validateSampler,
7
17
  validateScheduler
8
18
  } from '../lib/validation';
19
+ import { getVideoWorkflowType, isVideoModel, VIDEO_WORKFLOW_ASSETS } from './utils';
20
+ import { ApiError } from '../ApiClient';
21
+ import { ImageModelOptions, ModelOptions, VideoModelOptions } from './types/ModelOptions';
22
+
23
+ /**
24
+ * Validate that the provided assets match the workflow requirements.
25
+ * Throws an error if required assets are missing or forbidden assets are provided.
26
+ */
27
+ function validateVideoWorkflowAssets(params: VideoProjectParams): void {
28
+ const workflowType = getVideoWorkflowType(params.modelId);
29
+ if (!workflowType) return;
30
+
31
+ const requirements = VIDEO_WORKFLOW_ASSETS[workflowType];
32
+ if (!requirements) return;
33
+
34
+ // Special case for i2v: at least ONE of referenceImage or referenceImageEnd required
35
+ if (workflowType === 'i2v') {
36
+ if (!params.referenceImage && !params.referenceImageEnd) {
37
+ throw new ApiError(400, {
38
+ status: 'error',
39
+ errorCode: 0,
40
+ message:
41
+ 'i2v workflow requires at least one of referenceImage or referenceImageEnd. Please provide this asset.'
42
+ });
43
+ }
44
+ }
45
+
46
+ // Check for missing required assets and forbidden assets
47
+ for (const [asset, requirement] of Object.entries(requirements)) {
48
+ const assetKey = asset as keyof VideoProjectParams;
49
+ const hasAsset = !!params[assetKey];
50
+
51
+ if (requirement === 'required' && !hasAsset) {
52
+ throw new ApiError(400, {
53
+ status: 'error',
54
+ errorCode: 0,
55
+ message: `${workflowType} workflow requires ${assetKey}. Please provide this asset.`
56
+ });
57
+ }
58
+
59
+ if (requirement === 'forbidden' && hasAsset) {
60
+ throw new ApiError(400, {
61
+ status: 'error',
62
+ errorCode: 0,
63
+ message: `${workflowType} workflow does not support ${assetKey}. Please remove this asset.`
64
+ });
65
+ }
66
+ }
67
+ }
9
68
 
10
69
  // Mac worker can't process the data if some of the fields are missing, so we need to provide a default template
11
70
  function getTemplate() {
@@ -118,49 +177,190 @@ function getControlNet(params: ControlNetParams): ControlNetParamsRaw[] {
118
177
  return [cn];
119
178
  }
120
179
 
121
- function createJobRequestMessage(id: string, params: ProjectParams) {
180
+ function applyImageParams(
181
+ inputKeyframe: Record<string, any>,
182
+ params: ImageProjectParams,
183
+ options: ImageModelOptions
184
+ ) {
185
+ const keyFrame: Record<string, any> = {
186
+ ...inputKeyframe,
187
+ sizePreset: params.sizePreset,
188
+ hasContextImage1: !!params.contextImages?.[0],
189
+ hasContextImage2: !!params.contextImages?.[1],
190
+ hasContextImage3: !!params.contextImages?.[2],
191
+ hasContextImage4: !!params.contextImages?.[3],
192
+ hasContextImage5: !!params.contextImages?.[4],
193
+ hasContextImage6: !!params.contextImages?.[5]
194
+ };
195
+ // Sampler/scheduler handling: SDK validates and passes through as-is.
196
+ // sogni-socket normalizes values for both ComfyUI and Forge workers.
197
+ if (isComfyModel(params.modelId)) {
198
+ // ComfyUI models use comfySampler/comfyScheduler fields
199
+ keyFrame.comfySampler = validateSampler(params.sampler, options);
200
+ keyFrame.comfyScheduler = validateScheduler(params.scheduler, options);
201
+ } else {
202
+ // Legacy Forge models use scheduler/timeStepSpacing fields
203
+ keyFrame.scheduler = validateSampler(params.sampler, options);
204
+ keyFrame.timeStepSpacing = validateScheduler(params.scheduler, options);
205
+ }
206
+
207
+ if (params.startingImage) {
208
+ keyFrame.hasStartingImage = true;
209
+ keyFrame.strengthIsEnabled = true;
210
+ keyFrame.strength = 1 - (Number(params.startingImageStrength) || 0.5);
211
+ }
212
+
213
+ if (params.controlNet) {
214
+ keyFrame.currentControlNetsJob = getControlNet(params.controlNet);
215
+ }
216
+
217
+ // Set sizePreset to 'custom' if width/height are provided but sizePreset is not set
218
+ let effectiveSizePreset = params.sizePreset;
219
+ if (params.width && params.height && !params.sizePreset) {
220
+ effectiveSizePreset = 'custom';
221
+ }
222
+ keyFrame.sizePreset = effectiveSizePreset;
223
+
224
+ if (effectiveSizePreset === 'custom' && params.width && params.height) {
225
+ keyFrame.width = validateCustomImageSize(params.width);
226
+ keyFrame.height = validateCustomImageSize(params.height);
227
+ }
228
+ return keyFrame;
229
+ }
230
+
231
+ function applyVideoParams(
232
+ inputKeyframe: Record<string, any>,
233
+ params: VideoProjectParams,
234
+ options: VideoModelOptions
235
+ ) {
236
+ if (!isVideoModel(params.modelId)) {
237
+ throw new ApiError(400, {
238
+ status: 'error',
239
+ errorCode: 0,
240
+ message: 'Video generation is only supported for video models.'
241
+ });
242
+ }
243
+ validateVideoWorkflowAssets(params);
244
+ const keyFrame: Record<string, any> = { ...inputKeyframe };
245
+ if (params.referenceImage) {
246
+ keyFrame.hasReferenceImage = true;
247
+ }
248
+ if (params.referenceImageEnd) {
249
+ keyFrame.hasReferenceImageEnd = true;
250
+ }
251
+ if (params.referenceAudio) {
252
+ keyFrame.hasReferenceAudio = true;
253
+ }
254
+ if (params.referenceVideo) {
255
+ keyFrame.hasReferenceVideo = true;
256
+ }
257
+
258
+ // Video generation parameters
259
+ if (params.frames !== undefined) {
260
+ keyFrame.frames = params.frames;
261
+ }
262
+ if (params.duration !== undefined) {
263
+ const duration = validateVideoDuration(params.duration);
264
+ keyFrame.frames = duration * 16 + 1;
265
+ }
266
+ if (params.fps !== undefined) {
267
+ keyFrame.fps = params.fps;
268
+ }
269
+ if (params.shift !== undefined) {
270
+ keyFrame.shift = params.shift;
271
+ }
272
+ if (params.teacacheThreshold !== undefined) {
273
+ const validatedThreshold = validateTeacacheThreshold(params.teacacheThreshold);
274
+ if (validatedThreshold !== undefined) {
275
+ keyFrame.teacacheThreshold = validatedThreshold;
276
+ }
277
+ }
278
+
279
+ // S2V audio parameters
280
+ if (params.audioStart !== undefined) {
281
+ keyFrame.audioStart = params.audioStart;
282
+ }
283
+ if (params.audioDuration !== undefined) {
284
+ keyFrame.audioDuration = params.audioDuration;
285
+ }
286
+
287
+ // Animate video parameters (for animate-move, animate-replace)
288
+ if (params.videoStart !== undefined) {
289
+ keyFrame.videoStart = params.videoStart;
290
+ }
291
+
292
+ // Validate and set video dimensions (minimum 480px for Wan 2.2 models)
293
+ if (params.width && params.height) {
294
+ keyFrame.width = validateVideoSize(params.width, 'width');
295
+ keyFrame.height = validateVideoSize(params.height, 'height');
296
+ }
297
+
298
+ keyFrame.comfySampler = validateSampler(params.sampler, options);
299
+ keyFrame.comfyScheduler = validateScheduler(params.scheduler, options);
300
+
301
+ return keyFrame;
302
+ }
303
+
304
+ function createJobRequestMessage(id: string, params: ProjectParams, options: ModelOptions) {
122
305
  const template = getTemplate();
306
+ // Base keyFrame with common params
307
+ let keyFrame: Record<string, any> = {
308
+ ...template.keyFrames[0],
309
+ steps: params.steps,
310
+ guidanceScale: params.guidance,
311
+ modelID: params.modelId,
312
+ negativePrompt: params.negativePrompt,
313
+ seed: params.seed,
314
+ positivePrompt: params.positivePrompt,
315
+ stylePrompt: params.stylePrompt
316
+ };
317
+
318
+ switch (params.type) {
319
+ case 'image':
320
+ if (options.type !== 'image') {
321
+ throw new ApiError(400, {
322
+ status: 'error',
323
+ errorCode: 0,
324
+ message:
325
+ 'Invalid model type. Model does not support image generation. Please use a different model.'
326
+ });
327
+ }
328
+ keyFrame = applyImageParams(keyFrame, params, options);
329
+ break;
330
+ case 'video':
331
+ if (options.type !== 'video') {
332
+ throw new ApiError(400, {
333
+ status: 'error',
334
+ errorCode: 0,
335
+ message:
336
+ 'Invalid model type. Model does not support video generation. Please use a different model.'
337
+ });
338
+ }
339
+ keyFrame = applyVideoParams(keyFrame, params, options);
340
+ break;
341
+ default:
342
+ throw new ApiError(400, {
343
+ status: 'error',
344
+ errorCode: 0,
345
+ message: 'Invalid project type. Must be "image" or "video".'
346
+ });
347
+ }
348
+
123
349
  const jobRequest: Record<string, any> = {
124
350
  ...template,
125
- keyFrames: [
126
- {
127
- ...template.keyFrames[0],
128
- scheduler: validateSampler(params.sampler),
129
- timeStepSpacing: validateScheduler(params.scheduler),
130
- steps: params.steps,
131
- guidanceScale: params.guidance,
132
- modelID: params.modelId,
133
- negativePrompt: params.negativePrompt,
134
- seed: params.seed,
135
- positivePrompt: params.positivePrompt,
136
- stylePrompt: params.stylePrompt,
137
- hasStartingImage: !!params.startingImage,
138
- hasContextImage1: !!params.contextImages?.[0],
139
- hasContextImage2: !!params.contextImages?.[1],
140
- strengthIsEnabled: !!params.startingImage,
141
- strength: !!params.startingImage
142
- ? 1 - (Number(params.startingImageStrength) || 0.5)
143
- : undefined,
144
- sizePreset: params.sizePreset
145
- }
146
- ],
147
- previews: params.numberOfPreviews || 0,
148
- numberOfImages: params.numberOfImages,
351
+ keyFrames: [keyFrame],
352
+ previews: isImageParams(params) ? params.numberOfPreviews || 0 : 0,
353
+ numberOfImages: params.numberOfMedia || 1,
149
354
  jobID: id,
150
355
  disableSafety: !!params.disableNSFWFilter,
151
356
  tokenType: params.tokenType,
152
- outputFormat: params.outputFormat || 'png'
357
+ outputFormat: params.outputFormat || (isVideoParams(params) ? 'mp4' : 'png')
153
358
  };
359
+
154
360
  if (params.network) {
155
361
  jobRequest.network = params.network;
156
362
  }
157
- if (params.controlNet) {
158
- jobRequest.keyFrames[0].currentControlNetsJob = getControlNet(params.controlNet);
159
- }
160
- if (params.sizePreset === 'custom') {
161
- jobRequest.keyFrames[0].width = validateCustomImageSize(params.width);
162
- jobRequest.keyFrames[0].height = validateCustomImageSize(params.height);
163
- }
363
+
164
364
  return jobRequest;
165
365
  }
166
366