samsar-js 0.48.15 → 0.48.18

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
@@ -34,24 +34,99 @@ const samsar = new SamsarClient({ apiKey: process.env.SAMSAR_API_KEY! });
34
34
  const video = await samsar.createVideoFromText(
35
35
  {
36
36
  prompt: 'A drone shot of a beach at sunrise',
37
- image_model: 'GPTIMAGE1',
37
+ image_model: 'GPTIMAGE2',
38
38
  video_model: 'RUNWAYML',
39
39
  duration: 30,
40
40
  font_key: 'Poppins',
41
- enable_subtitles: false,
41
+ enable_subtitles: true,
42
42
  },
43
43
  { webhookUrl: 'https://example.com/webhook' },
44
44
  );
45
45
 
46
+ // Create a text video with a generated QR outro and bottom CTA footer
47
+ await samsar.createVideoFromText({
48
+ prompt: 'A boutique hotel launch reel with cinematic room details',
49
+ image_model: 'GPTIMAGE2',
50
+ video_model: 'RUNWAYML',
51
+ duration: 20,
52
+ aspect_ratio: '9:16',
53
+ generate_outro_image: true,
54
+ cta_url: 'https://example.com/book',
55
+ cta_text_top: 'Scan to book',
56
+ cta_text_bottom: 'Opening offers',
57
+ add_footer_animation: true,
58
+ footer_metadata: [{ url: 'https://example.com/book', title: 'Book your stay' }],
59
+ });
60
+
46
61
  // Create a video from an image list
47
62
  const videoFromImages = await samsar.createVideoFromImageList(
48
63
  {
49
64
  image_urls: ['https://example.com/a.jpg', 'https://example.com/b.jpg'],
50
65
  prompt: 'Cinematic sequence with smooth transitions',
51
66
  metadata: { project: 'demo' },
67
+ video_model: 'RUNWAYML',
68
+ aspect_ratio: '16:9',
52
69
  language: 'en',
53
70
  font_key: 'Poppins',
54
- enable_subtitles: false,
71
+ enable_subtitles: true,
72
+ },
73
+ { webhookUrl: 'https://example.com/webhook' },
74
+ );
75
+
76
+ // Create a video with a provided outro image
77
+ await samsar.createVideoFromImageList({
78
+ image_urls: ['https://example.com/a.jpg', 'https://example.com/b.jpg'],
79
+ prompt: 'Product launch teaser with a clean final CTA',
80
+ video_model: 'KLING3.0',
81
+ aspect_ratio: '16:9',
82
+ outro_image_url: 'https://cdn.example.com/outro.png',
83
+ add_outro_animation: true,
84
+ add_outro_focus_area: true,
85
+ outro_focust_area: { x: 680, y: 296, width: 432, height: 432 },
86
+ });
87
+
88
+ // Create a video and generate the QR outro server-side from the input images
89
+ await samsar.createVideoFromImageList({
90
+ image_urls: [
91
+ { image_url: 'https://example.com/a.jpg', title: 'Blue Lagoon Tour' },
92
+ { image_url: 'https://example.com/b.jpg', title: 'Sunset Dinner' },
93
+ ],
94
+ prompt: 'Travel offer reel with a scannable booking outro',
95
+ video_model: 'RUNWAYML',
96
+ aspect_ratio: '9:16',
97
+ generate_outro_image: true,
98
+ cta_url: 'https://example.com/book',
99
+ cta_text_top: 'Scan to book',
100
+ cta_text_bottom: 'Limited availability',
101
+ cta_logo: 'https://cdn.example.com/logo-white.png',
102
+ add_footer_animation: true,
103
+ footer_metadata: [
104
+ { url: 'https://example.com/blue-lagoon', title: 'Blue Lagoon Tour' },
105
+ { url: 'https://example.com/sunset-dinner', title: 'Sunset Dinner' },
106
+ ],
107
+ });
108
+
109
+ // Update an existing outro with a new provided outro image URL
110
+ await samsar.updateVideoOutroImage(
111
+ {
112
+ videoSessionId: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
113
+ outro_image_url: 'https://cdn.example.com/outro-v2.png',
114
+ add_outro_animation: true,
115
+ add_outro_focus_area: true,
116
+ outro_focust_area: { x: 680, y: 296, width: 432, height: 432 },
117
+ },
118
+ { webhookUrl: 'https://example.com/webhook' },
119
+ );
120
+
121
+ // Update an existing outro by generating a new QR CTA outro server-side
122
+ await samsar.updateVideoOutroImage(
123
+ {
124
+ videoSessionId: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
125
+ generate_outro_image: true,
126
+ cta_url: 'https://example.com/book',
127
+ cta_text_top: 'Scan to book',
128
+ cta_text_bottom: 'Limited availability',
129
+ add_outro_animation: true,
55
130
  },
56
131
  { webhookUrl: 'https://example.com/webhook' },
57
132
  );
@@ -267,6 +342,24 @@ completedSessions.data.forEach((session) => {
267
342
  console.log(session.session_id, session.langauge, session.result_url);
268
343
  });
269
344
 
345
+ // Publish, edit, or revoke a completed session in the public publication feed (free endpoints)
346
+ const publication = await samsar.publishPublication({
347
+ session_id: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
348
+ title: 'Running shoe teaser',
349
+ description: 'Launch-day vertical cut',
350
+ tags: ['launch', 'footwear'],
351
+ creator_handle: 'acme',
352
+ });
353
+ console.log(publication.data.publication?.publication_id);
354
+
355
+ await samsar.editPublication({
356
+ session_id: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
357
+ title: 'Running shoe teaser - updated',
358
+ tags: ['launch', 'footwear', 'campaign'],
359
+ });
360
+
361
+ await samsar.revokePublication(videoFromImages.data.session_id ?? videoFromImages.data.request_id!);
362
+
270
363
  // Image-specific status endpoint
271
364
  const rollupStatus = await samsar.getImageStatus(rollup.data.session_id);
272
365
 
@@ -324,7 +417,7 @@ await platform.setExternalAssistantSystemPrompt(
324
417
  // Create a render attributed to that external user
325
418
  const externalRender = await platform.createExternalVideoFromText(externalUser, {
326
419
  prompt: 'A sleek teaser for a futuristic running shoe',
327
- image_model: 'GPTIMAGE1',
420
+ image_model: 'GPTIMAGE2',
328
421
  video_model: 'RUNWAYML',
329
422
  duration: 10,
330
423
  enable_subtitles: true,
@@ -391,9 +484,15 @@ console.log(externalLibrary.data.requests.map((request) => request.request_id));
391
484
  ```
392
485
 
393
486
  Video model support notes:
394
- - `createVideoFromText` image model keys include: `GPTIMAGE1`, `IMAGEN4`, `SEEDREAM`, `HUNYUAN`, `NANOBANANA2`.
395
- - `createVideoFromText` supports all express video models: `RUNWAYML`, `KLINGIMGTOVID3PRO`, `HAILUO`, `HAILUOPRO`, `SEEDANCEI2V`, `VEO3.1I2V`, `VEO3.1I2VFAST`, `SORA2`, `SORA2PRO`.
396
- - `createVideoFromImageList` uses a fixed Veo2.1 pipeline model (`VEO3.1I2V`) and does not accept a `video_model` override.
487
+ - `createVideoFromText` image model keys include: `GPTIMAGE2`, `IMAGEN4`, `SEEDREAM`, `HUNYUAN`, `NANOBANANA2`.
488
+ - `createVideoFromText` supports all express video models: `RUNWAYML`, `KLINGIMGTOVID3PRO`, `HAILUO`, `HAILUOPRO`, `SEEDANCEI2V` (Seedance 2.0), `VEO3.1I2V`, `VEO3.1I2VFAST`, `SORA2`, `SORA2PRO`.
489
+ - `createVideoFromText` accepts either a provided outro (`outro_image_url`) or server-generated QR outro (`generate_outro_image: true` with `cta_url`). It can also render bottom CTA footer QR cards with `add_footer_animation` and `footer_metadata`; one footer item applies to every generated scene, while multiple items map by scene index.
490
+ - `createVideoFromImageList` supports `VEO3.1I2V`, `SEEDANCEI2V`, `KLING3.0`, and `RUNWAYML` via `video_model`; if omitted, it defaults to `VEO3.1I2V`. `KLINGIMGTOVID3PRO` is also accepted as a compatibility alias for `KLING3.0`. Use `aspect_ratio: '16:9'` or `'9:16'`; omitted or invalid values fall back to `16:9`.
491
+ - `createVideoFromImageList` accepts either a provided outro (`outro_image_url`) or server-generated QR outro (`generate_outro_image: true` with `cta_url`). Do not combine the two modes in a single request.
492
+ - `createVideoFromImageList` can render per-scene footer QR cards by setting `add_footer_animation: true` and providing one `footer_metadata` item per image scene.
493
+ - `updateVideoOutroImage` accepts either a replacement outro image URL (`outro_image_url`, `outroImageUrl`, `new_outro_image_url`) or a generated QR CTA outro (`generate_outro_image: true` with `cta_url`, or just `cta_url` when no outro image URL is supplied). Generated outro updates reuse the existing session image layers for tiling and only queue frame/video regeneration.
494
+ - `publishPublication`, `editPublication`, and `revokePublication` manage public feed publications for completed sessions through free `/publications/*` endpoints. They work with account API keys, customer sub-account API keys, and client auth tokens when the session belongs to the authenticated actor.
495
+ - Image-list video pricing is per rendered second: `VEO3.1I2V` and `SEEDANCEI2V` are 75 credits/sec, `KLING3.0` is 50 credits/sec, and `RUNWAYML` is 25 credits/sec.
397
496
 
398
497
  Each method returns `{ data, status, headers, creditsCharged, creditsRemaining, raw }`. Non-2xx responses throw `SamsarRequestError` containing status, body, and credit headers (if present).
399
498
 
package/dist/index.d.ts CHANGED
@@ -50,6 +50,30 @@ export interface CreateVideoFromTextInput {
50
50
  session_id?: string;
51
51
  sessionId?: string;
52
52
  sessionID?: string;
53
+ outro_image_url?: string;
54
+ outroImageUrl?: string;
55
+ add_outro_animation?: boolean;
56
+ addOutroAnimation?: boolean;
57
+ add_outro_focus_area?: boolean;
58
+ addOutroFocusArea?: boolean;
59
+ outro_focust_area?: OutroFocusArea | null;
60
+ outro_focus_area?: OutroFocusArea | null;
61
+ outroFocustArea?: OutroFocusArea | null;
62
+ outroFocusArea?: OutroFocusArea | null;
63
+ generate_outro_image?: boolean;
64
+ generateOutroImage?: boolean;
65
+ cta_url?: string;
66
+ ctaUrl?: string;
67
+ cta_text_top?: string;
68
+ ctaTextTop?: string;
69
+ cta_text_bottom?: string;
70
+ ctaTextBottom?: string;
71
+ cta_logo?: string;
72
+ ctaLogo?: string;
73
+ add_footer_animation?: boolean;
74
+ addFooterAnimation?: boolean;
75
+ footer_metadata?: FooterMetadataItem[];
76
+ footerMetadata?: FooterMetadataItem[];
53
77
  [key: string]: unknown;
54
78
  }
55
79
  export interface CreateVideoResponse {
@@ -64,6 +88,12 @@ export interface OutroFocusArea {
64
88
  width: number;
65
89
  height: number;
66
90
  }
91
+ export interface FooterMetadataItem {
92
+ url: string;
93
+ title?: string;
94
+ }
95
+ export type ImageListToVideoAspectRatio = '16:9' | '9:16';
96
+ export type ImageListToVideoModel = 'VEO3.1I2V' | 'SEEDANCEI2V' | 'KLING3.0' | 'KLINGIMGTOVID3PRO' | 'RUNWAYML';
67
97
  export interface ImageListToVideoItem {
68
98
  image_url?: string;
69
99
  imageUrl?: string;
@@ -79,12 +109,25 @@ export interface ImageListToVideoItem {
79
109
  fromEnhancedList?: boolean;
80
110
  skip_enhancement?: boolean;
81
111
  skipEnhancement?: boolean;
112
+ source?: string;
113
+ title?: string;
114
+ image_title?: string;
115
+ imageTitle?: string;
116
+ image_text?: string;
117
+ imageText?: string;
118
+ activity_title?: string;
119
+ activityTitle?: string;
120
+ name?: string;
121
+ label?: string;
82
122
  [key: string]: unknown;
83
123
  }
84
124
  export interface CreateVideoFromImageListInput {
85
125
  image_urls: Array<string | ImageListToVideoItem>;
86
126
  metadata?: Record<string, unknown>;
87
127
  prompt?: string;
128
+ video_model?: ImageListToVideoModel;
129
+ aspect_ratio?: ImageListToVideoAspectRatio;
130
+ aspectRatio?: ImageListToVideoAspectRatio;
88
131
  language?: string;
89
132
  languageString?: string | null;
90
133
  font_key?: string;
@@ -98,9 +141,29 @@ export interface CreateVideoFromImageListInput {
98
141
  sessionId?: string;
99
142
  sessionID?: string;
100
143
  outro_image_url?: string;
144
+ outroImageUrl?: string;
101
145
  add_outro_animation?: boolean;
146
+ addOutroAnimation?: boolean;
102
147
  add_outro_focus_area?: boolean;
148
+ addOutroFocusArea?: boolean;
103
149
  outro_focust_area?: OutroFocusArea | null;
150
+ outro_focus_area?: OutroFocusArea | null;
151
+ outroFocustArea?: OutroFocusArea | null;
152
+ outroFocusArea?: OutroFocusArea | null;
153
+ generate_outro_image?: boolean;
154
+ generateOutroImage?: boolean;
155
+ cta_url?: string;
156
+ ctaUrl?: string;
157
+ cta_text_top?: string;
158
+ ctaTextTop?: string;
159
+ cta_text_bottom?: string;
160
+ ctaTextBottom?: string;
161
+ cta_logo?: string;
162
+ ctaLogo?: string;
163
+ add_footer_animation?: boolean;
164
+ addFooterAnimation?: boolean;
165
+ footer_metadata?: FooterMetadataItem[];
166
+ footerMetadata?: FooterMetadataItem[];
104
167
  [key: string]: unknown;
105
168
  }
106
169
  export interface TranscriptBuilderPayload {
@@ -165,6 +228,24 @@ export interface UpdateVideoOutroImageInput {
165
228
  outroImageUrl?: string;
166
229
  new_outro_image_url?: string;
167
230
  newOutroImageUrl?: string;
231
+ add_outro_animation?: boolean;
232
+ addOutroAnimation?: boolean;
233
+ add_outro_focus_area?: boolean;
234
+ addOutroFocusArea?: boolean;
235
+ outro_focust_area?: OutroFocusArea | null;
236
+ outro_focus_area?: OutroFocusArea | null;
237
+ outroFocustArea?: OutroFocusArea | null;
238
+ outroFocusArea?: OutroFocusArea | null;
239
+ generate_outro_image?: boolean;
240
+ generateOutroImage?: boolean;
241
+ cta_url?: string;
242
+ ctaUrl?: string;
243
+ cta_text_top?: string;
244
+ ctaTextTop?: string;
245
+ cta_text_bottom?: string;
246
+ ctaTextBottom?: string;
247
+ cta_logo?: string;
248
+ ctaLogo?: string;
168
249
  [key: string]: unknown;
169
250
  }
170
251
  export interface UpdateVideoOutroImageResponse {
@@ -277,6 +358,96 @@ export interface CompletedVideoSession {
277
358
  [key: string]: unknown;
278
359
  }
279
360
  export type ListCompletedVideoSessionsResponse = CompletedVideoSession[];
361
+ export interface SessionPublicationInput {
362
+ session_id?: string;
363
+ sessionId?: string;
364
+ sessionID?: string;
365
+ video_session_id?: string;
366
+ videoSessionId?: string;
367
+ videoSessionID?: string;
368
+ id?: string;
369
+ title?: string;
370
+ description?: string;
371
+ tags?: string[] | string;
372
+ aspect_ratio?: string;
373
+ aspectRatio?: string;
374
+ creator_handle?: string;
375
+ creatorHandle?: string;
376
+ slug?: string;
377
+ image_hash?: string;
378
+ imageHash?: string;
379
+ splash_image?: string;
380
+ splashImage?: string;
381
+ image_model?: string;
382
+ imageModel?: string;
383
+ video_model?: string;
384
+ videoModel?: string;
385
+ original_prompt?: string;
386
+ originalPrompt?: string;
387
+ prompt?: string;
388
+ session_language?: string;
389
+ sessionLanguage?: string;
390
+ language?: string;
391
+ language_code?: string;
392
+ languageString?: string;
393
+ language_string?: string;
394
+ [key: string]: unknown;
395
+ }
396
+ export interface SessionPublicationSummary {
397
+ id?: string | null;
398
+ publication_id?: string | null;
399
+ session_id?: string | null;
400
+ sessionId?: string | null;
401
+ video_url?: string | null;
402
+ videoUrl?: string | null;
403
+ title?: string | null;
404
+ description?: string;
405
+ tags?: string[];
406
+ creator_handle?: string;
407
+ creatorHandle?: string;
408
+ slug?: string | null;
409
+ image_hash?: string | null;
410
+ imageHash?: string | null;
411
+ splash_image?: string | null;
412
+ splashImage?: string | null;
413
+ image_model?: string | null;
414
+ imageModel?: string | null;
415
+ video_model?: string | null;
416
+ videoModel?: string | null;
417
+ original_prompt?: string;
418
+ originalPrompt?: string;
419
+ session_language?: string | null;
420
+ sessionLanguage?: string | null;
421
+ language_string?: string | null;
422
+ languageString?: string | null;
423
+ aspect_ratio?: string | null;
424
+ aspectRatio?: string | null;
425
+ created_at?: string | null;
426
+ createdAt?: string | null;
427
+ updated_at?: string | null;
428
+ updatedAt?: string | null;
429
+ [key: string]: unknown;
430
+ }
431
+ export interface SessionPublicationSessionSummary {
432
+ id?: string | null;
433
+ session_id?: string | null;
434
+ is_published?: boolean;
435
+ published_publication_id?: string | null;
436
+ published_video_url?: string | null;
437
+ published_at?: string | null;
438
+ [key: string]: unknown;
439
+ }
440
+ export interface SessionPublicationResponse {
441
+ created?: boolean;
442
+ revoked?: boolean;
443
+ publication_id?: string | null;
444
+ publication?: SessionPublicationSummary | null;
445
+ session?: SessionPublicationSessionSummary | null;
446
+ sessionId?: string;
447
+ session_id?: string;
448
+ ispublishedVideo?: boolean;
449
+ [key: string]: unknown;
450
+ }
280
451
  export interface SupportedTextToVideoModelsResponse {
281
452
  IMAGE_MODELS: SupportedTextToVideoModelOption[];
282
453
  VIDEO_MODELS: SupportedTextToVideoModelOption[];
@@ -1432,6 +1603,33 @@ export declare class SamsarClient {
1432
1603
  listCompletedVideoSessions(options?: SamsarRequestOptions & {
1433
1604
  limit?: number;
1434
1605
  }): Promise<SamsarResult<ListCompletedVideoSessionsResponse>>;
1606
+ /**
1607
+ * Publish a completed Samsar video session to the public publication feed.
1608
+ * This is a free endpoint. The session must belong to the authenticated API key/auth token.
1609
+ */
1610
+ publishPublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1611
+ /**
1612
+ * Alias for publishPublication, named around the underlying VideoSession resource.
1613
+ */
1614
+ publishSessionPublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1615
+ /**
1616
+ * Edit an existing publication for a Samsar video session.
1617
+ * Omitted fields keep their current publication values.
1618
+ */
1619
+ editPublication(input: SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1620
+ /**
1621
+ * Alias for editPublication, named around the underlying VideoSession resource.
1622
+ */
1623
+ editSessionPublication(input: SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1624
+ /**
1625
+ * Revoke a publication for a Samsar video session.
1626
+ * This deletes the publication record and clears published fields on the session.
1627
+ */
1628
+ revokePublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1629
+ /**
1630
+ * Alias for revokePublication, named around the underlying VideoSession resource.
1631
+ */
1632
+ revokeSessionPublication(input: string | SessionPublicationInput, options?: SamsarRequestOptions): Promise<SamsarResult<SessionPublicationResponse>>;
1435
1633
  /**
1436
1634
  * Fetch the supported image/video model keys for the text_to_video route.
1437
1635
  * Maps to GET /video/supported_models.
package/dist/index.js CHANGED
@@ -15,6 +15,169 @@ export class SamsarRequestError extends Error {
15
15
  this.creditsRemaining = init.creditsRemaining;
16
16
  }
17
17
  }
18
+ function resolveAliasedInputValue(raw, aliases, canonicalName) {
19
+ let resolved;
20
+ let hasResolved = false;
21
+ for (const alias of aliases) {
22
+ const value = raw[alias];
23
+ if (value === undefined) {
24
+ continue;
25
+ }
26
+ if (!hasResolved) {
27
+ resolved = value;
28
+ hasResolved = true;
29
+ continue;
30
+ }
31
+ if (value !== resolved) {
32
+ throw new Error(`${canonicalName} was provided with conflicting alias values.`);
33
+ }
34
+ }
35
+ return hasResolved ? resolved : undefined;
36
+ }
37
+ function assertOptionalBoolean(value, fieldName, context = 'createVideoFromImageList') {
38
+ if (value !== undefined && typeof value !== 'boolean') {
39
+ throw new Error(`${fieldName} must be a boolean for ${context}`);
40
+ }
41
+ }
42
+ function normalizeCreateVideoFromTextInput(input) {
43
+ const raw = input;
44
+ const normalized = { ...input };
45
+ const aliases = [
46
+ ['session_id', ['session_id', 'sessionId', 'sessionID']],
47
+ ['aspect_ratio', ['aspect_ratio', 'aspectRatio']],
48
+ ['outro_image_url', ['outro_image_url', 'outroImageUrl']],
49
+ ['add_outro_animation', ['add_outro_animation', 'addOutroAnimation']],
50
+ ['add_outro_focus_area', ['add_outro_focus_area', 'addOutroFocusArea']],
51
+ ['outro_focust_area', ['outro_focust_area', 'outro_focus_area', 'outroFocustArea', 'outroFocusArea']],
52
+ ['generate_outro_image', ['generate_outro_image', 'generateOutroImage']],
53
+ ['cta_url', ['cta_url', 'ctaUrl']],
54
+ ['cta_text_top', ['cta_text_top', 'ctaTextTop']],
55
+ ['cta_text_bottom', ['cta_text_bottom', 'ctaTextBottom']],
56
+ ['cta_logo', ['cta_logo', 'ctaLogo']],
57
+ ['add_footer_animation', ['add_footer_animation', 'addFooterAnimation']],
58
+ ['footer_metadata', ['footer_metadata', 'footerMetadata']],
59
+ ['enable_subtitles', ['enable_subtitles', 'enableSubtitles']],
60
+ ['font_key', ['font_key', 'fontKey']],
61
+ ];
62
+ for (const [canonicalName, aliasList] of aliases) {
63
+ const value = resolveAliasedInputValue(raw, aliasList, canonicalName);
64
+ if (value !== undefined) {
65
+ normalized[canonicalName] = value;
66
+ }
67
+ }
68
+ assertOptionalBoolean(normalized.enable_subtitles, 'enable_subtitles', 'createVideoFromText');
69
+ assertOptionalBoolean(normalized.add_outro_animation, 'add_outro_animation', 'createVideoFromText');
70
+ assertOptionalBoolean(normalized.add_outro_focus_area, 'add_outro_focus_area', 'createVideoFromText');
71
+ assertOptionalBoolean(normalized.generate_outro_image, 'generate_outro_image', 'createVideoFromText');
72
+ assertOptionalBoolean(normalized.add_footer_animation, 'add_footer_animation', 'createVideoFromText');
73
+ if (normalized.generate_outro_image === true) {
74
+ const ctaUrl = typeof normalized.cta_url === 'string' ? normalized.cta_url.trim() : '';
75
+ if (!ctaUrl) {
76
+ throw new Error('cta_url is required when generate_outro_image is true for createVideoFromText');
77
+ }
78
+ }
79
+ else if (normalized.add_outro_focus_area === true && normalized.add_outro_animation !== true) {
80
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for createVideoFromText');
81
+ }
82
+ return normalized;
83
+ }
84
+ function normalizeCreateVideoFromImageListInput(input) {
85
+ const raw = input;
86
+ if (!Array.isArray(input.image_urls) || input.image_urls.length === 0) {
87
+ throw new Error('image_urls must be a non-empty array for createVideoFromImageList');
88
+ }
89
+ const normalized = { ...input };
90
+ const aliases = [
91
+ ['session_id', ['session_id', 'sessionId', 'sessionID']],
92
+ ['aspect_ratio', ['aspect_ratio', 'aspectRatio']],
93
+ ['outro_image_url', ['outro_image_url', 'outroImageUrl']],
94
+ ['add_outro_animation', ['add_outro_animation', 'addOutroAnimation']],
95
+ ['add_outro_focus_area', ['add_outro_focus_area', 'addOutroFocusArea']],
96
+ ['outro_focust_area', ['outro_focust_area', 'outro_focus_area', 'outroFocustArea', 'outroFocusArea']],
97
+ ['generate_outro_image', ['generate_outro_image', 'generateOutroImage']],
98
+ ['cta_url', ['cta_url', 'ctaUrl']],
99
+ ['cta_text_top', ['cta_text_top', 'ctaTextTop']],
100
+ ['cta_text_bottom', ['cta_text_bottom', 'ctaTextBottom']],
101
+ ['cta_logo', ['cta_logo', 'ctaLogo']],
102
+ ['add_footer_animation', ['add_footer_animation', 'addFooterAnimation']],
103
+ ['footer_metadata', ['footer_metadata', 'footerMetadata']],
104
+ ['enable_subtitles', ['enable_subtitles', 'enableSubtitles']],
105
+ ['font_key', ['font_key', 'fontKey']],
106
+ ];
107
+ for (const [canonicalName, aliasList] of aliases) {
108
+ const value = resolveAliasedInputValue(raw, aliasList, canonicalName);
109
+ if (value !== undefined) {
110
+ normalized[canonicalName] = value;
111
+ }
112
+ }
113
+ assertOptionalBoolean(normalized.enable_subtitles, 'enable_subtitles');
114
+ assertOptionalBoolean(normalized.add_outro_animation, 'add_outro_animation');
115
+ assertOptionalBoolean(normalized.add_outro_focus_area, 'add_outro_focus_area');
116
+ assertOptionalBoolean(normalized.generate_outro_image, 'generate_outro_image');
117
+ assertOptionalBoolean(normalized.add_footer_animation, 'add_footer_animation');
118
+ if (normalized.generate_outro_image === true) {
119
+ const ctaUrl = typeof normalized.cta_url === 'string' ? normalized.cta_url.trim() : '';
120
+ if (!ctaUrl) {
121
+ throw new Error('cta_url is required when generate_outro_image is true for createVideoFromImageList');
122
+ }
123
+ }
124
+ else if (normalized.add_outro_focus_area === true && normalized.add_outro_animation !== true) {
125
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for createVideoFromImageList');
126
+ }
127
+ return normalized;
128
+ }
129
+ function normalizeSessionPublicationInput(input, context) {
130
+ if (typeof input !== 'string' && (!input || typeof input !== 'object' || Array.isArray(input))) {
131
+ throw new Error(`input must be a session id or object for ${context}`);
132
+ }
133
+ const raw = typeof input === 'string' ? { session_id: input } : { ...input };
134
+ const sessionId = resolveAliasedInputValue(raw, [
135
+ 'session_id',
136
+ 'sessionId',
137
+ 'sessionID',
138
+ 'video_session_id',
139
+ 'videoSessionId',
140
+ 'videoSessionID',
141
+ 'id',
142
+ ], 'session_id');
143
+ const normalizedSessionId = typeof sessionId === 'string' ? sessionId.trim() : '';
144
+ if (!normalizedSessionId) {
145
+ throw new Error(`session_id is required for ${context}`);
146
+ }
147
+ const normalized = {
148
+ ...raw,
149
+ session_id: normalizedSessionId,
150
+ };
151
+ const aliases = [
152
+ ['aspect_ratio', ['aspect_ratio', 'aspectRatio']],
153
+ ['creator_handle', ['creator_handle', 'creatorHandle']],
154
+ ['image_hash', ['image_hash', 'imageHash']],
155
+ ['splash_image', ['splash_image', 'splashImage']],
156
+ ['image_model', ['image_model', 'imageModel']],
157
+ ['video_model', ['video_model', 'videoModel']],
158
+ ['original_prompt', ['original_prompt', 'originalPrompt', 'prompt']],
159
+ ['session_language', ['session_language', 'sessionLanguage', 'language', 'language_code']],
160
+ ['language_string', ['language_string', 'languageString']],
161
+ ];
162
+ for (const [canonicalName, aliasList] of aliases) {
163
+ const value = resolveAliasedInputValue(raw, aliasList, canonicalName);
164
+ if (value !== undefined) {
165
+ normalized[canonicalName] = value;
166
+ }
167
+ }
168
+ if (normalized.tags !== undefined &&
169
+ !Array.isArray(normalized.tags) &&
170
+ typeof normalized.tags !== 'string') {
171
+ throw new Error(`tags must be a string or string array for ${context}`);
172
+ }
173
+ if (Array.isArray(normalized.tags)) {
174
+ normalized.tags = normalized.tags
175
+ .filter((tag) => typeof tag === 'string')
176
+ .map((tag) => tag.trim())
177
+ .filter(Boolean);
178
+ }
179
+ return normalized;
180
+ }
18
181
  export class SamsarClient {
19
182
  constructor(options) {
20
183
  if (!options?.apiKey) {
@@ -34,8 +197,9 @@ export class SamsarClient {
34
197
  * Create a new video generation job from text.
35
198
  */
36
199
  async createVideoFromText(input, options) {
200
+ const normalizedInput = normalizeCreateVideoFromTextInput(input);
37
201
  const body = {
38
- input,
202
+ input: normalizedInput,
39
203
  webhookUrl: options?.webhookUrl,
40
204
  };
41
205
  return this.post('video/create', body, options);
@@ -44,8 +208,9 @@ export class SamsarClient {
44
208
  * Create a video from a list of image URLs with optional prompt/metadata via the image_list_to_video route.
45
209
  */
46
210
  async createVideoFromImageList(input, options) {
211
+ const normalizedInput = normalizeCreateVideoFromImageListInput(input);
47
212
  const body = {
48
- input,
213
+ input: normalizedInput,
49
214
  webhookUrl: options?.webhookUrl,
50
215
  };
51
216
  const response = await this.post('video/image_list_to_video', body, options);
@@ -73,9 +238,10 @@ export class SamsarClient {
73
238
  * Create a text-to-video request attributed to an external user while billing against the shared API key.
74
239
  */
75
240
  async createExternalVideoFromText(externalUser, input, options) {
241
+ const normalizedInput = normalizeCreateVideoFromTextInput(input);
76
242
  const body = {
77
243
  external_user: normalizeExternalUserIdentity(externalUser),
78
- input,
244
+ input: normalizedInput,
79
245
  webhookUrl: options?.webhookUrl,
80
246
  };
81
247
  return this.post('external_users/text_to_video', body, options);
@@ -99,9 +265,10 @@ export class SamsarClient {
99
265
  * Create an image-list-to-video request attributed to an external user while billing against the shared API key.
100
266
  */
101
267
  async createExternalVideoFromImageList(externalUser, input, options) {
268
+ const normalizedInput = normalizeCreateVideoFromImageListInput(input);
102
269
  const body = {
103
270
  external_user: normalizeExternalUserIdentity(externalUser),
104
- input,
271
+ input: normalizedInput,
105
272
  webhookUrl: options?.webhookUrl,
106
273
  };
107
274
  return this.post('external_users/image_list_to_video', body, options);
@@ -409,17 +576,75 @@ export class SamsarClient {
409
576
  raw.outroImageUrl ??
410
577
  raw.new_outro_image_url ??
411
578
  raw.newOutroImageUrl;
579
+ const rawGenerateOutroImage = raw.generate_outro_image ??
580
+ raw.generateOutroImage;
581
+ const rawAddOutroAnimation = raw.add_outro_animation ??
582
+ raw.addOutroAnimation;
583
+ const rawAddOutroFocusArea = raw.add_outro_focus_area ??
584
+ raw.addOutroFocusArea;
585
+ const rawOutroFocusArea = raw.outro_focust_area ??
586
+ raw.outro_focus_area ??
587
+ raw.outroFocustArea ??
588
+ raw.outroFocusArea;
589
+ const ctaUrl = raw.cta_url ??
590
+ raw.ctaUrl;
591
+ const ctaTextTop = raw.cta_text_top ??
592
+ raw.ctaTextTop;
593
+ const ctaTextBottom = raw.cta_text_bottom ??
594
+ raw.ctaTextBottom;
595
+ const ctaLogo = raw.cta_logo ??
596
+ raw.ctaLogo;
597
+ const generateOutroImage = rawGenerateOutroImage === true ||
598
+ (rawGenerateOutroImage === undefined && !outroImageUrl && Boolean(ctaUrl));
412
599
  if (!videoSessionId) {
413
600
  throw new Error('videoSessionId is required for updateVideoOutroImage');
414
601
  }
415
- if (!outroImageUrl) {
416
- throw new Error('outro_image_url is required for updateVideoOutroImage');
602
+ if (rawGenerateOutroImage !== undefined && typeof rawGenerateOutroImage !== 'boolean') {
603
+ throw new Error('generate_outro_image must be a boolean for updateVideoOutroImage');
604
+ }
605
+ if (rawAddOutroAnimation !== undefined && typeof rawAddOutroAnimation !== 'boolean') {
606
+ throw new Error('add_outro_animation must be a boolean for updateVideoOutroImage');
607
+ }
608
+ if (rawAddOutroFocusArea !== undefined && typeof rawAddOutroFocusArea !== 'boolean') {
609
+ throw new Error('add_outro_focus_area must be a boolean for updateVideoOutroImage');
610
+ }
611
+ if (generateOutroImage && outroImageUrl) {
612
+ throw new Error('Use either generate_outro_image with cta_url or outro_image_url for updateVideoOutroImage');
613
+ }
614
+ if (!generateOutroImage && !outroImageUrl) {
615
+ throw new Error('outro_image_url is required for updateVideoOutroImage unless generate_outro_image is true');
616
+ }
617
+ if (generateOutroImage) {
618
+ if (!ctaUrl || !String(ctaUrl).trim()) {
619
+ throw new Error('cta_url is required when generate_outro_image is true for updateVideoOutroImage');
620
+ }
621
+ }
622
+ else if (rawAddOutroFocusArea === true && rawAddOutroAnimation !== true) {
623
+ throw new Error('add_outro_focus_area requires add_outro_animation to be true for updateVideoOutroImage');
624
+ }
625
+ if (!generateOutroImage && rawAddOutroFocusArea === true) {
626
+ if (!rawOutroFocusArea || typeof rawOutroFocusArea !== 'object' || Array.isArray(rawOutroFocusArea)) {
627
+ throw new Error('outro_focust_area must be an object with x, y, width, height for updateVideoOutroImage');
628
+ }
629
+ const { x, y, width, height } = rawOutroFocusArea;
630
+ const isInvalid = [x, y, width, height].some((value) => typeof value !== 'number' || !Number.isFinite(value));
631
+ if (isInvalid) {
632
+ throw new Error('outro_focust_area x, y, width, height must be valid numbers for updateVideoOutroImage');
633
+ }
417
634
  }
418
635
  const body = {
419
636
  input: {
420
637
  ...input,
421
638
  videoSessionId: String(videoSessionId),
422
- outro_image_url: String(outroImageUrl),
639
+ ...(outroImageUrl ? { outro_image_url: String(outroImageUrl) } : {}),
640
+ generate_outro_image: generateOutroImage,
641
+ ...(generateOutroImage ? { cta_url: String(ctaUrl).trim() } : {}),
642
+ ...(ctaTextTop ? { cta_text_top: String(ctaTextTop) } : {}),
643
+ ...(ctaTextBottom ? { cta_text_bottom: String(ctaTextBottom) } : {}),
644
+ ...(ctaLogo ? { cta_logo: String(ctaLogo) } : {}),
645
+ ...(rawAddOutroAnimation !== undefined ? { add_outro_animation: rawAddOutroAnimation === true } : {}),
646
+ ...(rawAddOutroFocusArea !== undefined ? { add_outro_focus_area: rawAddOutroFocusArea === true } : {}),
647
+ ...(rawOutroFocusArea !== undefined ? { outro_focust_area: rawOutroFocusArea } : {}),
423
648
  },
424
649
  webhookUrl: options?.webhookUrl,
425
650
  };
@@ -601,6 +826,48 @@ export class SamsarClient {
601
826
  data: normalizedData,
602
827
  };
603
828
  }
829
+ /**
830
+ * Publish a completed Samsar video session to the public publication feed.
831
+ * This is a free endpoint. The session must belong to the authenticated API key/auth token.
832
+ */
833
+ async publishPublication(input, options) {
834
+ const payload = normalizeSessionPublicationInput(input, 'publishPublication');
835
+ return this.post('publications/publish', payload, options);
836
+ }
837
+ /**
838
+ * Alias for publishPublication, named around the underlying VideoSession resource.
839
+ */
840
+ async publishSessionPublication(input, options) {
841
+ return this.publishPublication(input, options);
842
+ }
843
+ /**
844
+ * Edit an existing publication for a Samsar video session.
845
+ * Omitted fields keep their current publication values.
846
+ */
847
+ async editPublication(input, options) {
848
+ const payload = normalizeSessionPublicationInput(input, 'editPublication');
849
+ return this.post('publications/edit', payload, options);
850
+ }
851
+ /**
852
+ * Alias for editPublication, named around the underlying VideoSession resource.
853
+ */
854
+ async editSessionPublication(input, options) {
855
+ return this.editPublication(input, options);
856
+ }
857
+ /**
858
+ * Revoke a publication for a Samsar video session.
859
+ * This deletes the publication record and clears published fields on the session.
860
+ */
861
+ async revokePublication(input, options) {
862
+ const payload = normalizeSessionPublicationInput(input, 'revokePublication');
863
+ return this.post('publications/revoke', payload, options);
864
+ }
865
+ /**
866
+ * Alias for revokePublication, named around the underlying VideoSession resource.
867
+ */
868
+ async revokeSessionPublication(input, options) {
869
+ return this.revokePublication(input, options);
870
+ }
604
871
  /**
605
872
  * Fetch the supported image/video model keys for the text_to_video route.
606
873
  * Maps to GET /video/supported_models.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "samsar-js",
3
- "version": "0.48.15",
3
+ "version": "0.48.18",
4
4
  "description": "TypeScript client for the Samsar Processor API routes.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",