samsar-js 0.48.29 → 0.48.31

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
@@ -107,6 +107,20 @@ await samsar.createVideoFromImageList({
107
107
  ],
108
108
  });
109
109
 
110
+ // Create the same generated QR outro and per-scene footer CTAs from one CTA link
111
+ await samsar.createVideoFromImageList({
112
+ image_urls: [
113
+ { image_url: 'https://example.com/a.jpg', title: 'Blue Lagoon Tour' },
114
+ { image_url: 'https://example.com/b.jpg', title: 'Sunset Dinner' },
115
+ ],
116
+ prompt: 'Travel offer reel with concise scene CTAs',
117
+ metadata: { brand: 'Guidestination', offer: 'Bookable Bangkok experiences' },
118
+ video_model: 'RUNWAYML',
119
+ aspect_ratio: '9:16',
120
+ express_cta_generation: true,
121
+ cta_url: 'https://example.com/book',
122
+ });
123
+
110
124
  // Update an existing outro with a new provided outro image URL
111
125
  await samsar.updateVideoOutroImage(
112
126
  {
@@ -332,6 +346,24 @@ const images = await samsar.extendImageList({
332
346
  aspect_ratio: '16:9',
333
347
  });
334
348
 
349
+ // Assign a short SEO-friendly image title
350
+ const imageTitle = await samsar.assignImageTitle({
351
+ image_url: 'https://example.com/product-photo.png',
352
+ metadata: {
353
+ product: 'linen travel shirt',
354
+ collection: 'spring essentials',
355
+ },
356
+ });
357
+ console.log(imageTitle.data.content); // "Linen Travel Shirt"
358
+
359
+ // Binary uploads are also supported in browser/Node 18+ runtimes
360
+ const fileTitle = await samsar.assignImageTitle({
361
+ image: fileOrBlob,
362
+ fileName: 'product-photo.png',
363
+ mimeType: 'image/png',
364
+ metadata: { product: 'linen travel shirt' },
365
+ });
366
+
335
367
  // Create a reusable receipt template from one sample receipt image (free endpoint)
336
368
  const receiptTemplate = await samsar.createReceiptTemplate({
337
369
  image_url: 'https://example.com/receipt-template.png',
@@ -547,11 +579,12 @@ console.log(externalLibrary.data.requests.map((request) => request.request_id));
547
579
 
548
580
  Video model support notes:
549
581
  - `createVideoFromText` image model keys include: `GPTIMAGE2`, `IMAGEN4`, `SEEDREAM`, `NANOBANANA2`, `CUSTOM_TEXT_TO_IMAGE`.
550
- - `createVideoFromText` supports these video models: `RUNWAYML`, `VEO3.1I2V`, `VEO3.1I2VFAST`, `SEEDANCEI2V` (Seedance 1.5), `KLINGIMGTOVID3PRO`, and `CUSTOM_IMAGE_TO_VIDEO`.
582
+ - `createVideoFromText` supports these video models: `RUNWAYML`, `VEO3.1I2V`, `VEO3.1I2VFAST`, `SEEDANCEI2V` (Seedance 1.5), `KLINGIMGTOVID3PRO`, and `HAPPYHORSEI2V`.
551
583
  - `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.
552
- - `createVideoFromImageList` supports `RUNWAYML`, `VEO3.1I2V`, `VEO3.1I2VFAST`, `SEEDANCEI2V`, `KLINGIMGTOVID3PRO`, and `CUSTOM_IMAGE_TO_VIDEO` via `video_model`; if omitted, it defaults to `RUNWAYML`. Use `aspect_ratio: '16:9'` or `'9:16'`; omitted or invalid values fall back to `16:9`.
584
+ - `createVideoFromImageList` supports `RUNWAYML`, `VEO3.1I2V`, `VEO3.1I2VFAST`, `SEEDANCEI2V`, `KLINGIMGTOVID3PRO`, and `HAPPYHORSEI2V` via `video_model`; if omitted, it defaults to `RUNWAYML`. Use `aspect_ratio: '16:9'` or `'9:16'`; omitted or invalid values fall back to `16:9`.
553
585
  - `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.
554
586
  - `createVideoFromImageList` can render per-scene footer QR cards by setting `add_footer_animation: true` and providing one `footer_metadata` item per image scene.
587
+ - `createVideoFromImageList` can also generate QR outro CTA text and each scene footer CTA from a single link by setting `express_cta_generation: true` with `cta_url`. CamelCase `expressCtaGeneration` and compatibility aliases `auto_generate_cta_text` / `generate_cta_texts` are normalized to the same API field.
555
588
  - `createVideoFromImageList` accepts `limit_single_narrator: true` to keep all narration under one narrator identity. `add_narrator_avatar: true` automatically enables `limit_single_narrator`, generates an influencer-style human narrator avatar, and overlays it bottom-center or centered in the footer row when footer metadata is present.
556
589
  - `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.
557
590
  - `updateVideoFooterImage` updates the footer CTA on a cloned session with `cta_text`, `cta_logo`, and/or `cta_url`, or removes all scene footers with `remove_footer: true`. Footer updates queue only frame/video regeneration.
@@ -559,13 +592,13 @@ Video model support notes:
559
592
  - Main video methods and external-user methods accept the same generated outro and footer parameters. The API can resolve either internal session ids or external `extreq_...` ids on repeated video routes, so client code can keep using `translateVideo`, `joinVideos`, `addSubtitles`, `removeSubtitles`, `addVideoOutroImage`, `updateVideoOutroImage`, and `updateVideoFooterImage`; the explicit external variants are available when you want to call `/external_users/*` directly. Do not strip the `extreq_` prefix.
560
593
  - Completed video status, latest-version, and completed-session list responses expose `has_subtitles` and `result_language` when the session metadata is available.
561
594
  - `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.
562
- - Text-to-video and image-list video pricing use the same per-rendered-second rates for standard express models: `VEO3.1I2V` is 60 credits/sec, `VEO3.1I2VFAST` is 36 credits/sec, `SEEDANCEI2V` is 30 credits/sec, `KLINGIMGTOVID3PRO` is 36 credits/sec, and `RUNWAYML` is 30 credits/sec. Image-list narrator avatar generation adds 4 credits/sec when `add_narrator_avatar` is true.
563
- - Standard express video models expose a per-second pricing distribution through `EXPRESS_VIDEO_PRICING_DISTRIBUTION_PER_SECOND_BY_MODEL`: pipeline 4, inference 4, image gen/edit 2, speech 2, music 2, effects and lipsync 2, and video as the model-specific remainder.
595
+ - Text-to-video and image-list video pricing use the same per-rendered-second rates for standard express models: `VEO3.1I2V` is 60 credits/sec, `VEO3.1I2VFAST` is 36 credits/sec, `SEEDANCEI2V` is 30 credits/sec, `KLINGIMGTOVID3PRO` is 36 credits/sec, `HAPPYHORSEI2V` is 36 credits/sec, and `RUNWAYML` is 30 credits/sec. Image-list narrator avatar generation adds 4 credits/sec when `add_narrator_avatar` is true; express CTA generation adds 1 credit/sec when `express_cta_generation` is true.
596
+ - Standard express video models expose a per-second pricing distribution through `EXPRESS_VIDEO_PRICING_DISTRIBUTION_PER_SECOND_BY_MODEL`: pipeline 4, inference 4, image gen/edit 2, speech 2, music 2, effects and lipsync 2, video as the model-specific remainder, and `optionalAddons.express_cta_generation` at 1 credit/sec.
564
597
 
565
598
  Upcoming `/v2` omni route adapters:
566
599
  - `/v2` is additive; `/v1` is not deprecated.
567
- - `createV2VideoFromText`, `createV2VideoFromImageList`, `translateV2Video`, `cloneV2Video`, `regenerateV2VideoAvatar`, `updateV2VideoOutroImage`, `updateV2VideoFooterImage`, `addV2VideoOutroImage`, `getV2Status`, `getV2StatusDetailed`, `getV2Credits`, `listV2Requests`, and `createV2Session` call the new omni route surface.
568
- - Step-controlled video helpers include `createV2StepVideoFromText`, `createV2StepTextToVideo`, `createV2StepVideoFromImage`, `createV2StepImageToVideo`, `getV2StepVideoStatus`, `getV2StepVideoStatusDetailed`, and `processNextV2StepVideo`.
600
+ - `createV2VideoFromText`, `createV2VideoFromImageList`, `assignV2ImageTitle`, `translateV2Video`, `cloneV2Video`, `regenerateV2VideoAvatar`, `updateV2VideoOutroImage`, `updateV2VideoFooterImage`, `addV2VideoOutroImage`, `getV2Status`, `getV2StatusDetailed`, `getV2Credits`, `listV2Requests`, and `createV2Session` call the new omni route surface.
601
+ - Step-controlled video helpers include `createV2StepVideoFromText`, `createV2StepTextToVideo`, `createV2StepVideoFromImage`, `createV2StepImageToVideo`, `getV2StepVideoStatus`, `getV2StepVideoStatusDetailed`, and `processNextV2StepVideo`. They default to 1-step express rendering by sending `auto_render_full_video: true` and `manual_step_stages: []`; pass `{ stepMode: 'two_step' }` or `manual_step_stages: ['ai_video_generation']` to require an explicit second-step approval before image-to-video generation.
569
602
  - Programmatic user helpers include `createV2ExternalUser`, `createV2UserRechargeCredits`, `refreshV2UserToken`, `createV2UserAppKey`, `refreshV2UserAppKey`, `getV2UserCredits`, `getV2UserUsageLogs`, and `getV2UserPaymentStatus`.
570
603
  - Omit `externalUser` for internal account billing, pass `externalUser` to scope an external user with the account API key, or authenticate the client directly with an external-user auth token/API key. V2 external users can be referenced by `unique_key`; if `unique_key` is omitted during creation, the server uses `external_user_id` as the key.
571
604
  - Detailed status adapters return the normal status payload plus a normalized `session` preview shape with `layers`, `audioLayers`, `globalAudioLayers`, and `globalVideos`. Layer timing uses `startTime` and `endTime` so clients can preview generated images, scene clips, and speech before final render completion.
@@ -574,9 +607,8 @@ Upcoming `/v2` omni route adapters:
574
607
  const v2Video = await platform.createV2VideoFromImageList({
575
608
  image_urls: ['https://cdn.example.com/a.png', 'https://cdn.example.com/b.png'],
576
609
  video_model: 'RUNWAYML',
577
- generate_outro_image: true,
610
+ express_cta_generation: true,
578
611
  cta_url: 'https://example.com/book',
579
- cta_text_top: 'Scan to book',
580
612
  });
581
613
 
582
614
  const v2ExternalVideo = await platform.createV2VideoFromImageList(
@@ -587,6 +619,12 @@ const v2ExternalVideo = await platform.createV2VideoFromImageList(
587
619
  { externalUser },
588
620
  );
589
621
 
622
+ const v2ImageTitle = await platform.assignV2ImageTitle({
623
+ image_url: 'https://cdn.example.com/product-frame.png',
624
+ metadata: { product: 'travel shirt', channel: 'marketplace' },
625
+ });
626
+ console.log(v2ImageTitle.data.content);
627
+
590
628
  const v2Translated = await platform.translateV2Video({
591
629
  videoSessionId: v2Video.data.request_id!,
592
630
  language: 'es',
@@ -610,7 +648,7 @@ const v2Detailed = await platform.getV2StatusDetailed(v2Video.data.request_id!);
610
648
  console.log(v2Detailed.data.session?.previewStage, v2Detailed.data.session?.layers?.[0]?.preview?.url);
611
649
  ```
612
650
 
613
- Step-controlled video generation pauses after each completed stage until you call `processNextV2StepVideo`:
651
+ Step video generation defaults to a 1-step express render, so it does not pause before image-to-video generation:
614
652
 
615
653
  ```ts
616
654
  const stepVideo = await platform.createV2StepVideoFromText({
@@ -622,12 +660,28 @@ const stepVideo = await platform.createV2StepVideoFromText({
622
660
  enable_subtitles: true,
623
661
  });
624
662
 
625
- let stepStatus = await platform.getV2StepVideoStatus(stepVideo.data.request_id);
626
- if (stepStatus.data.step_status === 'COMPLETED') {
627
- console.log(stepStatus.data.current_step, stepStatus.data.current_step_resources);
628
- const stepDetailed = await platform.getV2StepVideoStatusDetailed(stepVideo.data.request_id);
629
- console.log(stepDetailed.data.session?.previewStage, stepDetailed.data.session?.layers?.[0]?.preview?.url);
630
- stepStatus = await platform.processNextV2StepVideo(stepVideo.data.request_id);
663
+ const stepDetailed = await platform.getV2StepVideoStatusDetailed(stepVideo.data.request_id);
664
+ console.log(stepDetailed.data.session?.previewStage, stepDetailed.data.session?.layers?.[0]?.preview?.url);
665
+ ```
666
+
667
+ Use 2-step mode only when you want to review generated image/audio assets before starting the image-to-video stage:
668
+
669
+ ```ts
670
+ const twoStepVideo = await platform.createV2StepVideoFromText(
671
+ {
672
+ prompt: 'A 20 second launch teaser for a new travel app',
673
+ image_model: 'GPTIMAGE2',
674
+ video_model: 'RUNWAYML',
675
+ duration: 20,
676
+ aspect_ratio: '16:9',
677
+ enable_subtitles: true,
678
+ },
679
+ { stepMode: 'two_step' },
680
+ );
681
+
682
+ let stepStatus = await platform.getV2StepVideoStatus(twoStepVideo.data.request_id);
683
+ if (stepStatus.data.waiting_for_process_next || stepStatus.data.can_process_next) {
684
+ stepStatus = await platform.processNextV2StepVideo(twoStepVideo.data.request_id);
631
685
  }
632
686
 
633
687
  const stepImageVideo = await platform.createV2StepVideoFromImage({
@@ -9,7 +9,12 @@ export declare const EXPRESS_VIDEO_FIXED_PRICING_COMPONENTS_PER_SECOND: {
9
9
  export type ExpressVideoPricingDistribution = typeof EXPRESS_VIDEO_FIXED_PRICING_COMPONENTS_PER_SECOND & {
10
10
  video: number;
11
11
  total: number;
12
+ optionalAddons: ExpressVideoOptionalAddons;
12
13
  };
14
+ export declare const EXPRESS_VIDEO_OPTIONAL_ADDON_CREDITS_PER_SECOND: {
15
+ readonly express_cta_generation: 1;
16
+ };
17
+ export type ExpressVideoOptionalAddons = typeof EXPRESS_VIDEO_OPTIONAL_ADDON_CREDITS_PER_SECOND;
13
18
  export declare const EXPRESS_VIDEO_FIXED_COMPONENTS_TOTAL_PER_SECOND: number;
14
19
  export declare const EXPRESS_VIDEO_CREDITS_PER_SECOND_BY_MODEL: {
15
20
  readonly 'VEO3.1I2V': 60;
@@ -17,6 +22,7 @@ export declare const EXPRESS_VIDEO_CREDITS_PER_SECOND_BY_MODEL: {
17
22
  readonly SEEDANCEI2V: 30;
18
23
  readonly KLINGIMGTOVID3PRO: 36;
19
24
  readonly KLINGIMGTOVIDTURBO: 36;
25
+ readonly HAPPYHORSEI2V: 36;
20
26
  readonly RUNWAYML: 30;
21
27
  };
22
28
  export declare const EXPRESS_VIDEO_PRICING_DISTRIBUTION_PER_SECOND_BY_MODEL: Record<string, ExpressVideoPricingDistribution>;
@@ -6,6 +6,9 @@ export const EXPRESS_VIDEO_FIXED_PRICING_COMPONENTS_PER_SECOND = {
6
6
  music: 2,
7
7
  effects_and_lipsync: 2,
8
8
  };
9
+ export const EXPRESS_VIDEO_OPTIONAL_ADDON_CREDITS_PER_SECOND = {
10
+ express_cta_generation: 1,
11
+ };
9
12
  export const EXPRESS_VIDEO_FIXED_COMPONENTS_TOTAL_PER_SECOND = Object.values(EXPRESS_VIDEO_FIXED_PRICING_COMPONENTS_PER_SECOND).reduce((total, value) => total + value, 0);
10
13
  export const EXPRESS_VIDEO_CREDITS_PER_SECOND_BY_MODEL = {
11
14
  'VEO3.1I2V': 60,
@@ -13,6 +16,7 @@ export const EXPRESS_VIDEO_CREDITS_PER_SECOND_BY_MODEL = {
13
16
  SEEDANCEI2V: 30,
14
17
  KLINGIMGTOVID3PRO: 36,
15
18
  KLINGIMGTOVIDTURBO: 36,
19
+ HAPPYHORSEI2V: 36,
16
20
  RUNWAYML: 30,
17
21
  };
18
22
  export const EXPRESS_VIDEO_PRICING_DISTRIBUTION_PER_SECOND_BY_MODEL = Object.fromEntries(Object.entries(EXPRESS_VIDEO_CREDITS_PER_SECOND_BY_MODEL)
@@ -22,6 +26,7 @@ export const EXPRESS_VIDEO_PRICING_DISTRIBUTION_PER_SECOND_BY_MODEL = Object.fro
22
26
  ...EXPRESS_VIDEO_FIXED_PRICING_COMPONENTS_PER_SECOND,
23
27
  video: total - EXPRESS_VIDEO_FIXED_COMPONENTS_TOTAL_PER_SECOND,
24
28
  total,
29
+ optionalAddons: EXPRESS_VIDEO_OPTIONAL_ADDON_CREDITS_PER_SECOND,
25
30
  },
26
31
  ]));
27
32
  export function getExpressVideoCreditsPerSecond(model) {
package/dist/index.d.ts CHANGED
@@ -36,7 +36,14 @@ export interface FontOptions {
36
36
  language?: string;
37
37
  family?: string;
38
38
  }
39
- export interface CreateVideoFromTextInput {
39
+ export type V2StepGenerationMode = 'one_step' | 'two_step';
40
+ export interface V2StepGenerationOptions {
41
+ auto_render_full_video?: boolean;
42
+ autoRenderFullVideo?: boolean;
43
+ manual_step_stages?: Array<V2StepVideoStage | string>;
44
+ manualStepStages?: Array<V2StepVideoStage | string>;
45
+ }
46
+ export interface CreateVideoFromTextInput extends V2StepGenerationOptions {
40
47
  prompt: string;
41
48
  image_model: string;
42
49
  video_model: string;
@@ -111,7 +118,7 @@ export interface FooterMetadataItem {
111
118
  footerLogoImagePath?: string;
112
119
  }
113
120
  export type ImageListToVideoAspectRatio = '16:9' | '9:16';
114
- export type ImageListToVideoModel = 'RUNWAYML' | 'VEO3.1I2V' | 'VEO3.1I2VFAST' | 'SEEDANCEI2V' | 'KLINGIMGTOVID3PRO' | 'CUSTOM_IMAGE_TO_VIDEO';
121
+ export type ImageListToVideoModel = 'RUNWAYML' | 'VEO3.1I2V' | 'VEO3.1I2VFAST' | 'SEEDANCEI2V' | 'KLINGIMGTOVID3PRO' | 'HAPPYHORSEI2V';
115
122
  export interface ImageListToVideoItem {
116
123
  image_url?: string;
117
124
  imageUrl?: string;
@@ -139,7 +146,7 @@ export interface ImageListToVideoItem {
139
146
  label?: string;
140
147
  [key: string]: unknown;
141
148
  }
142
- export interface CreateVideoFromImageListInput {
149
+ export interface CreateVideoFromImageListInput extends V2StepGenerationOptions {
143
150
  image_urls: Array<string | ImageListToVideoItem>;
144
151
  metadata?: Record<string, unknown>;
145
152
  prompt?: string;
@@ -172,6 +179,12 @@ export interface CreateVideoFromImageListInput {
172
179
  outroFocusArea?: OutroFocusArea | null;
173
180
  generate_outro_image?: boolean;
174
181
  generateOutroImage?: boolean;
182
+ express_cta_generation?: boolean;
183
+ expressCtaGeneration?: boolean;
184
+ auto_generate_cta_text?: boolean;
185
+ autoGenerateCtaText?: boolean;
186
+ generate_cta_texts?: boolean;
187
+ generateCtaTexts?: boolean;
175
188
  cta_url?: string;
176
189
  ctaUrl?: string;
177
190
  cta_text_top?: string;
@@ -962,6 +975,33 @@ export interface EnhanceImageResponse {
962
975
  remainingCredits?: number;
963
976
  [key: string]: unknown;
964
977
  }
978
+ export type AssignImageTitleMimeType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif';
979
+ export type AssignImageTitleBinaryInput = Blob | ArrayBuffer | Uint8Array;
980
+ export interface AssignImageTitleRequest {
981
+ image?: string | AssignImageTitleBinaryInput;
982
+ file?: AssignImageTitleBinaryInput;
983
+ image_file?: AssignImageTitleBinaryInput;
984
+ imageFile?: AssignImageTitleBinaryInput;
985
+ image_url?: string;
986
+ imageUrl?: string;
987
+ url?: string;
988
+ image_data?: string;
989
+ imageData?: string;
990
+ image_data_url?: string;
991
+ imageDataUrl?: string;
992
+ metadata?: Record<string, unknown> | string;
993
+ metadata_json?: Record<string, unknown> | string;
994
+ metadataJson?: Record<string, unknown> | string;
995
+ fileName?: string;
996
+ filename?: string;
997
+ mimeType?: AssignImageTitleMimeType | string;
998
+ [key: string]: unknown;
999
+ }
1000
+ export interface AssignImageTitleResponse {
1001
+ content: string;
1002
+ title?: string;
1003
+ [key: string]: unknown;
1004
+ }
965
1005
  export interface ExtendImageListRequest {
966
1006
  image_urls: string[];
967
1007
  num_images: number;
@@ -1411,6 +1451,9 @@ export interface V2RequestOptions extends SamsarRequestOptions {
1411
1451
  externalUser?: V2ExternalUserIdentity | null;
1412
1452
  webhookUrl?: string;
1413
1453
  }
1454
+ export interface V2StepVideoRequestOptions extends V2RequestOptions, V2StepGenerationOptions {
1455
+ stepMode?: V2StepGenerationMode;
1456
+ }
1414
1457
  export type V2StepVideoStage = 'prompt_generation' | 'image_generation' | 'speech_generation' | 'music_generation' | 'audio_generation' | 'ai_video_generation' | 'lip_sync_generation' | 'sound_effect_generation' | 'narrator_avatar_generation' | 'delete_reflow' | 'timeline_reflowed' | 'transcript_generation' | 'frame_generation' | 'video_generation';
1415
1458
  export type V2StepVideoStatus = 'INIT' | 'PENDING' | 'COMPLETED' | 'FAILED' | string;
1416
1459
  export interface VideoSessionPreviewAsset {
@@ -1541,7 +1584,11 @@ export interface V2StepVideoState {
1541
1584
  current_step?: V2StepVideoStage | string | null;
1542
1585
  current_step_label?: string | null;
1543
1586
  next_step?: V2StepVideoStage | string | null;
1587
+ manual_step_stages?: Array<V2StepVideoStage | string>;
1588
+ auto_advance_step_stages?: Array<V2StepVideoStage | string>;
1544
1589
  waiting_for_process_next?: boolean;
1590
+ requires_user_action?: boolean;
1591
+ can_process_next?: boolean;
1545
1592
  updated_at?: string | null;
1546
1593
  [key: string]: unknown;
1547
1594
  }
@@ -1944,10 +1991,10 @@ export declare class SamsarClient {
1944
1991
  }): Promise<SamsarResult<CreateLoginTokenResponse | ExternalCreateLoginTokenResponse>>;
1945
1992
  createV2VideoFromText(input: CreateVideoFromTextInput, options?: V2RequestOptions): Promise<SamsarResult<CreateVideoResponse | ExternalRequestResponse>>;
1946
1993
  createV2VideoFromImageList(input: CreateVideoFromImageListInput, options?: V2RequestOptions): Promise<SamsarResult<CreateVideoFromImageListResponse | ExternalRequestResponse>>;
1947
- createV2StepVideoFromText(input: CreateVideoFromTextInput, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1948
- createV2StepTextToVideo(input: CreateVideoFromTextInput, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1949
- createV2StepVideoFromImage(input: CreateV2StepImageToVideoInput, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1950
- createV2StepImageToVideo(input: CreateV2StepImageToVideoInput, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1994
+ createV2StepVideoFromText(input: CreateVideoFromTextInput, options?: V2StepVideoRequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1995
+ createV2StepTextToVideo(input: CreateVideoFromTextInput, options?: V2StepVideoRequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1996
+ createV2StepVideoFromImage(input: CreateV2StepImageToVideoInput, options?: V2StepVideoRequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1997
+ createV2StepImageToVideo(input: CreateV2StepImageToVideoInput, options?: V2StepVideoRequestOptions): Promise<SamsarResult<V2StepVideoCreateResponse>>;
1951
1998
  getV2StepVideoStatus(requestId: string, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoStatusResponse>>;
1952
1999
  getV2StepVideoStatusDetailed(requestId: string, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoDetailedStatusResponse>>;
1953
2000
  getV2StepVideoDetailedStatus(requestId: string, options?: V2RequestOptions): Promise<SamsarResult<V2StepVideoDetailedStatusResponse>>;
@@ -1958,6 +2005,12 @@ export declare class SamsarClient {
1958
2005
  uploadV2ImageData(imageData: string[], options?: V2RequestOptions): Promise<SamsarResult<{
1959
2006
  image_urls: string[];
1960
2007
  }>>;
2008
+ /**
2009
+ * Assign a short SEO-friendly title to an image using the /v2 image route.
2010
+ * Supports image_url/image_data JSON payloads or binary image/FormData uploads.
2011
+ */
2012
+ assignV2ImageTitle(payload: AssignImageTitleRequest | FormData, options?: V2RequestOptions): Promise<SamsarResult<AssignImageTitleResponse>>;
2013
+ assignV2TitleToImage(payload: AssignImageTitleRequest | FormData, options?: V2RequestOptions): Promise<SamsarResult<AssignImageTitleResponse>>;
1961
2014
  updateV2VideoOutroImage(input: UpdateVideoOutroImageInput, options?: V2RequestOptions): Promise<SamsarResult<UpdateVideoOutroImageResponse | ExternalRequestResponse>>;
1962
2015
  updateV2VideoFooterImage(input: UpdateVideoFooterImageInput, options?: V2RequestOptions): Promise<SamsarResult<UpdateVideoFooterImageResponse | ExternalRequestResponse>>;
1963
2016
  addV2VideoOutroImage(input: AddVideoOutroImageInput, options?: V2RequestOptions): Promise<SamsarResult<AddVideoOutroImageResponse | ExternalRequestResponse>>;
@@ -2243,6 +2296,12 @@ export declare class SamsarClient {
2243
2296
  * Enhance an image by upscaling it with the specified resolution.
2244
2297
  */
2245
2298
  enhanceImage(payload: EnhanceImageRequest, options?: SamsarRequestOptions): Promise<SamsarResult<EnhanceImageResponse>>;
2299
+ /**
2300
+ * Assign a short SEO-friendly title to an image.
2301
+ * Supports image_url/image_data JSON payloads or binary image/FormData uploads.
2302
+ */
2303
+ assignImageTitle(payload: AssignImageTitleRequest | FormData, options?: SamsarRequestOptions): Promise<SamsarResult<AssignImageTitleResponse>>;
2304
+ assignTitleToImage(payload: AssignImageTitleRequest | FormData, options?: SamsarRequestOptions): Promise<SamsarResult<AssignImageTitleResponse>>;
2246
2305
  /**
2247
2306
  * Add or extend a saved image list for the authenticated API key.
2248
2307
  */
@@ -2359,6 +2418,7 @@ export declare class SamsarClient {
2359
2418
  }): Promise<SamsarResult<GlobalStatusResponse>>;
2360
2419
  private get;
2361
2420
  private post;
2421
+ private postForm;
2362
2422
  private request;
2363
2423
  private buildHeaders;
2364
2424
  private buildUrl;
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ const DEBUG = (() => {
4
4
  const env = globalThis?.process?.env;
5
5
  return env?.SAMSAR_SDK_DEBUG === '1';
6
6
  })();
7
+ const DEFAULT_V2_STEP_MANUAL_STAGES = ['ai_video_generation'];
7
8
  export class SamsarRequestError extends Error {
8
9
  constructor(message, init) {
9
10
  super(message);
@@ -40,6 +41,85 @@ function assertOptionalBoolean(value, fieldName, context = 'createVideoFromImage
40
41
  throw new Error(`${fieldName} must be a boolean for ${context}`);
41
42
  }
42
43
  }
44
+ function getFirstDefinedInputValue(raw, aliases) {
45
+ for (const alias of aliases) {
46
+ if (Object.prototype.hasOwnProperty.call(raw, alias)) {
47
+ return raw[alias];
48
+ }
49
+ }
50
+ return undefined;
51
+ }
52
+ function normalizeV2StepGenerationMode(value) {
53
+ if (typeof value !== 'string') {
54
+ return null;
55
+ }
56
+ const normalized = value.trim().toLowerCase().replace(/\s+/g, '_');
57
+ if (['one_step', 'one-step', '1_step', '1-step', 'express', 'auto', 'automatic'].includes(normalized)) {
58
+ return 'one_step';
59
+ }
60
+ if (['two_step', 'two-step', '2_step', '2-step', 'manual'].includes(normalized)) {
61
+ return 'two_step';
62
+ }
63
+ return null;
64
+ }
65
+ function normalizeV2StepBoolean(value, fieldName) {
66
+ if (value === undefined) {
67
+ return undefined;
68
+ }
69
+ if (typeof value === 'boolean') {
70
+ return value;
71
+ }
72
+ throw new Error(`${fieldName} must be a boolean for step video generation`);
73
+ }
74
+ function normalizeV2ManualStepStages(value) {
75
+ if (value === undefined) {
76
+ return undefined;
77
+ }
78
+ if (value === false || value === null) {
79
+ return [];
80
+ }
81
+ if (Array.isArray(value)) {
82
+ return value
83
+ .filter((stage) => typeof stage === 'string' && stage.trim().length > 0)
84
+ .map((stage) => stage.trim());
85
+ }
86
+ if (typeof value === 'string') {
87
+ return value
88
+ .split(',')
89
+ .map((stage) => stage.trim())
90
+ .filter(Boolean);
91
+ }
92
+ if (typeof value === 'object') {
93
+ return Object.entries(value)
94
+ .filter(([, enabled]) => enabled === true || enabled === 'true' || enabled === 1 || enabled === '1')
95
+ .map(([stage]) => stage.trim())
96
+ .filter(Boolean);
97
+ }
98
+ throw new Error('manual_step_stages must be an array, comma-separated string, stage map, null, or false');
99
+ }
100
+ function buildV2StepGenerationFields(input, options) {
101
+ const optionRecord = (options ?? {});
102
+ const stepMode = normalizeV2StepGenerationMode(optionRecord.stepMode);
103
+ const optionAutoRender = normalizeV2StepBoolean(getFirstDefinedInputValue(optionRecord, ['auto_render_full_video', 'autoRenderFullVideo']), 'auto_render_full_video');
104
+ const inputAutoRender = normalizeV2StepBoolean(getFirstDefinedInputValue(input, ['auto_render_full_video', 'autoRenderFullVideo']), 'auto_render_full_video');
105
+ const optionManualStages = normalizeV2ManualStepStages(getFirstDefinedInputValue(optionRecord, ['manual_step_stages', 'manualStepStages']));
106
+ const inputManualStages = normalizeV2ManualStepStages(getFirstDefinedInputValue(input, ['manual_step_stages', 'manualStepStages']));
107
+ let autoRenderFullVideo = stepMode ? stepMode === 'one_step' : optionAutoRender ?? inputAutoRender;
108
+ if (autoRenderFullVideo === undefined) {
109
+ autoRenderFullVideo = optionManualStages !== undefined || inputManualStages !== undefined
110
+ ? false
111
+ : true;
112
+ }
113
+ const manualStepStages = autoRenderFullVideo
114
+ ? []
115
+ : optionManualStages ?? inputManualStages ?? [...DEFAULT_V2_STEP_MANUAL_STAGES];
116
+ return {
117
+ auto_render_full_video: autoRenderFullVideo,
118
+ autoRenderFullVideo,
119
+ manual_step_stages: manualStepStages,
120
+ manualStepStages: manualStepStages,
121
+ };
122
+ }
43
123
  function normalizeCreateVideoFromTextInput(input) {
44
124
  const raw = input;
45
125
  const normalized = { ...input };
@@ -96,6 +176,17 @@ function normalizeCreateVideoFromImageListInput(input) {
96
176
  ['add_outro_focus_area', ['add_outro_focus_area', 'addOutroFocusArea']],
97
177
  ['outro_focust_area', ['outro_focust_area', 'outro_focus_area', 'outroFocustArea', 'outroFocusArea']],
98
178
  ['generate_outro_image', ['generate_outro_image', 'generateOutroImage']],
179
+ [
180
+ 'express_cta_generation',
181
+ [
182
+ 'express_cta_generation',
183
+ 'expressCtaGeneration',
184
+ 'auto_generate_cta_text',
185
+ 'autoGenerateCtaText',
186
+ 'generate_cta_texts',
187
+ 'generateCtaTexts',
188
+ ],
189
+ ],
99
190
  ['cta_url', ['cta_url', 'ctaUrl']],
100
191
  ['cta_text_top', ['cta_text_top', 'ctaTextTop']],
101
192
  ['cta_text_bottom', ['cta_text_bottom', 'ctaTextBottom']],
@@ -117,12 +208,24 @@ function normalizeCreateVideoFromImageListInput(input) {
117
208
  assertOptionalBoolean(normalized.add_outro_animation, 'add_outro_animation');
118
209
  assertOptionalBoolean(normalized.add_outro_focus_area, 'add_outro_focus_area');
119
210
  assertOptionalBoolean(normalized.generate_outro_image, 'generate_outro_image');
211
+ assertOptionalBoolean(normalized.express_cta_generation, 'express_cta_generation');
120
212
  assertOptionalBoolean(normalized.add_footer_animation, 'add_footer_animation');
121
213
  assertOptionalBoolean(normalized.limit_single_narrator, 'limit_single_narrator');
122
214
  assertOptionalBoolean(normalized.add_narrator_avatar, 'add_narrator_avatar');
123
215
  if (normalized.add_narrator_avatar === true) {
124
216
  normalized.limit_single_narrator = true;
125
217
  }
218
+ if (normalized.express_cta_generation === true) {
219
+ const ctaUrl = typeof normalized.cta_url === 'string' ? normalized.cta_url.trim() : '';
220
+ if (!ctaUrl) {
221
+ throw new Error('cta_url is required when express_cta_generation is true for createVideoFromImageList');
222
+ }
223
+ normalized.cta_url = ctaUrl;
224
+ normalized.generate_outro_image = true;
225
+ normalized.add_outro_animation = true;
226
+ normalized.add_outro_focus_area = true;
227
+ normalized.add_footer_animation = true;
228
+ }
126
229
  if (normalized.generate_outro_image === true) {
127
230
  const ctaUrl = typeof normalized.cta_url === 'string' ? normalized.cta_url.trim() : '';
128
231
  if (!ctaUrl) {
@@ -606,8 +709,13 @@ export class SamsarClient {
606
709
  }
607
710
  async createV2StepVideoFromText(input, options) {
608
711
  const normalizedInput = normalizeCreateVideoFromTextInput(input);
712
+ const stepGenerationFields = buildV2StepGenerationFields(normalizedInput, options);
609
713
  return this.postV2('video/step/text_to_video', {
610
- input: normalizedInput,
714
+ ...stepGenerationFields,
715
+ input: {
716
+ ...normalizedInput,
717
+ ...stepGenerationFields,
718
+ },
611
719
  webhookUrl: options?.webhookUrl,
612
720
  }, options);
613
721
  }
@@ -616,8 +724,13 @@ export class SamsarClient {
616
724
  }
617
725
  async createV2StepVideoFromImage(input, options) {
618
726
  const normalizedInput = normalizeCreateV2StepImageToVideoInput(input);
727
+ const stepGenerationFields = buildV2StepGenerationFields(normalizedInput, options);
619
728
  return this.postV2('video/step/image_to_video', {
620
- input: normalizedInput,
729
+ ...stepGenerationFields,
730
+ input: {
731
+ ...normalizedInput,
732
+ ...stepGenerationFields,
733
+ },
621
734
  webhookUrl: options?.webhookUrl,
622
735
  }, options);
623
736
  }
@@ -679,6 +792,22 @@ export class SamsarClient {
679
792
  },
680
793
  }, options);
681
794
  }
795
+ /**
796
+ * Assign a short SEO-friendly title to an image using the /v2 image route.
797
+ * Supports image_url/image_data JSON payloads or binary image/FormData uploads.
798
+ */
799
+ async assignV2ImageTitle(payload, options) {
800
+ const body = buildAssignImageTitleBody(payload);
801
+ if (isFormDataBody(body)) {
802
+ return this.postForm(this.buildV2Url('image/assign_title'), body, options);
803
+ }
804
+ return this.postV2('image/assign_title', {
805
+ input: body,
806
+ }, options);
807
+ }
808
+ async assignV2TitleToImage(payload, options) {
809
+ return this.assignV2ImageTitle(payload, options);
810
+ }
682
811
  async updateV2VideoOutroImage(input, options) {
683
812
  const normalizedInput = normalizeUpdateVideoOutroImageInput(input, 'updateV2VideoOutroImage');
684
813
  return this.postV2('update_outro_image', {
@@ -1645,6 +1774,20 @@ export class SamsarClient {
1645
1774
  async enhanceImage(payload, options) {
1646
1775
  return this.post('image/enhance', payload, options);
1647
1776
  }
1777
+ /**
1778
+ * Assign a short SEO-friendly title to an image.
1779
+ * Supports image_url/image_data JSON payloads or binary image/FormData uploads.
1780
+ */
1781
+ async assignImageTitle(payload, options) {
1782
+ const body = buildAssignImageTitleBody(payload);
1783
+ if (isFormDataBody(body)) {
1784
+ return this.postForm('image/assign_title', body, options);
1785
+ }
1786
+ return this.post('image/assign_title', body, options);
1787
+ }
1788
+ async assignTitleToImage(payload, options) {
1789
+ return this.assignImageTitle(payload, options);
1790
+ }
1648
1791
  /**
1649
1792
  * Add or extend a saved image list for the authenticated API key.
1650
1793
  */
@@ -2019,6 +2162,13 @@ export class SamsarClient {
2019
2162
  body: JSON.stringify(body),
2020
2163
  });
2021
2164
  }
2165
+ async postForm(path, body, options) {
2166
+ return this.request(path, {
2167
+ ...(options ?? {}),
2168
+ method: 'POST',
2169
+ body,
2170
+ });
2171
+ }
2022
2172
  async request(path, options) {
2023
2173
  const url = this.buildUrl(path, options.query);
2024
2174
  const controller = new AbortController();
@@ -2108,7 +2258,7 @@ export class SamsarClient {
2108
2258
  : this.apiKey
2109
2259
  ? `Bearer ${this.apiKey}`
2110
2260
  : undefined,
2111
- 'Content-Type': options.body ? 'application/json' : undefined,
2261
+ 'Content-Type': options.body && !isFormDataBody(options.body) ? 'application/json' : undefined,
2112
2262
  'x-external-user-api-key': options.externalUserApiKey ?? this.externalUserApiKey,
2113
2263
  'x-app-secret': resolvedAppKey ? resolvedAppSecret : undefined,
2114
2264
  ...this.defaultHeaders,
@@ -2175,6 +2325,104 @@ export class SamsarClient {
2175
2325
  function trimTrailingSlash(url) {
2176
2326
  return url.replace(/\/+$/, '');
2177
2327
  }
2328
+ function buildAssignImageTitleBody(payload) {
2329
+ if (isFormDataBody(payload)) {
2330
+ return payload;
2331
+ }
2332
+ const input = (payload ?? {});
2333
+ const image = input.image ??
2334
+ input.file ??
2335
+ input.image_file ??
2336
+ input.imageFile;
2337
+ if (isBinaryAssignImageTitleInput(image)) {
2338
+ return buildAssignImageTitleFormData(input, image);
2339
+ }
2340
+ const stringImage = getTrimmedString(image);
2341
+ const imageData = stringImage?.startsWith('data:image/')
2342
+ ? stringImage
2343
+ : getTrimmedString(input.image_data) ??
2344
+ getTrimmedString(input.imageData) ??
2345
+ getTrimmedString(input.image_data_url) ??
2346
+ getTrimmedString(input.imageDataUrl);
2347
+ const imageUrl = stringImage && !stringImage.startsWith('data:image/')
2348
+ ? stringImage
2349
+ : getTrimmedString(input.image_url) ??
2350
+ getTrimmedString(input.imageUrl) ??
2351
+ getTrimmedString(input.url);
2352
+ const metadata = input.metadata ?? input.metadata_json ?? input.metadataJson;
2353
+ if (!imageData && !imageUrl) {
2354
+ throw new Error('image, image_data, or image_url is required for assignImageTitle');
2355
+ }
2356
+ return {
2357
+ ...(imageData ? { image_data: imageData } : {}),
2358
+ ...(imageUrl ? { image_url: imageUrl } : {}),
2359
+ ...(metadata !== undefined ? { metadata } : {}),
2360
+ };
2361
+ }
2362
+ function buildAssignImageTitleFormData(input, image) {
2363
+ if (typeof FormData === 'undefined') {
2364
+ throw new Error('FormData is required for binary assignImageTitle uploads');
2365
+ }
2366
+ const metadata = input.metadata ?? input.metadata_json ?? input.metadataJson;
2367
+ const mimeType = getTrimmedString(input.mimeType) ?? getBlobType(image);
2368
+ if (!mimeType || !isSupportedAssignImageTitleMimeType(mimeType)) {
2369
+ throw new Error('mimeType must be image/png, image/jpeg, image/webp, or image/gif for binary assignImageTitle uploads');
2370
+ }
2371
+ const blob = toAssignImageTitleBlob(image, mimeType);
2372
+ const fileName = getTrimmedString(input.fileName) ??
2373
+ getTrimmedString(input.filename) ??
2374
+ `image.${mimeTypeToExtension(mimeType)}`;
2375
+ const formData = new FormData();
2376
+ formData.append('image', blob, fileName);
2377
+ if (metadata !== undefined) {
2378
+ formData.append('metadata', typeof metadata === 'string' ? metadata : JSON.stringify(metadata));
2379
+ }
2380
+ return formData;
2381
+ }
2382
+ function isBinaryAssignImageTitleInput(value) {
2383
+ return (isBlobValue(value) ||
2384
+ value instanceof ArrayBuffer ||
2385
+ value instanceof Uint8Array);
2386
+ }
2387
+ function toAssignImageTitleBlob(value, mimeType) {
2388
+ if (isBlobValue(value)) {
2389
+ if (value.type === mimeType) {
2390
+ return value;
2391
+ }
2392
+ return new Blob([value], { type: mimeType });
2393
+ }
2394
+ if (value instanceof Uint8Array) {
2395
+ const arrayBuffer = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
2396
+ return new Blob([arrayBuffer], { type: mimeType });
2397
+ }
2398
+ return new Blob([value], { type: mimeType });
2399
+ }
2400
+ function getBlobType(value) {
2401
+ return isBlobValue(value) ? getTrimmedString(value.type) : undefined;
2402
+ }
2403
+ function isBlobValue(value) {
2404
+ return typeof Blob !== 'undefined' && value instanceof Blob;
2405
+ }
2406
+ function isFormDataBody(body) {
2407
+ return typeof FormData !== 'undefined' && body instanceof FormData;
2408
+ }
2409
+ function isSupportedAssignImageTitleMimeType(value) {
2410
+ const normalized = value.trim().toLowerCase();
2411
+ return ['image/png', 'image/jpeg', 'image/jpg', 'image/webp', 'image/gif'].includes(normalized);
2412
+ }
2413
+ function mimeTypeToExtension(value) {
2414
+ const normalized = value.trim().toLowerCase();
2415
+ if (normalized === 'image/jpeg' || normalized === 'image/jpg') {
2416
+ return 'jpg';
2417
+ }
2418
+ if (normalized === 'image/webp') {
2419
+ return 'webp';
2420
+ }
2421
+ if (normalized === 'image/gif') {
2422
+ return 'gif';
2423
+ }
2424
+ return 'png';
2425
+ }
2178
2426
  function parseMaybeJson(value) {
2179
2427
  try {
2180
2428
  return JSON.parse(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "samsar-js",
3
- "version": "0.48.29",
3
+ "version": "0.48.31",
4
4
  "description": "TypeScript client for the Samsar Processor API routes.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",