samsar-js 0.48.24 → 0.48.26

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
@@ -153,8 +153,17 @@ const translated = await samsar.translateVideo(
153
153
  {
154
154
  videoSessionId: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
155
155
  language: 'es',
156
- // Optional: override the outro image in the translated session
157
- outro_image_url: 'https://cdn.example.com/outro.png',
156
+ enable_subtitles: false,
157
+ translate_outro: true,
158
+ translate_footer: true,
159
+ },
160
+ { webhookUrl: 'https://example.com/webhook' },
161
+ );
162
+
163
+ // Deep-clone an existing completed session and render a new final video URL
164
+ const cloned = await samsar.cloneVideo(
165
+ {
166
+ videoSessionId: videoFromImages.data.session_id ?? videoFromImages.data.request_id!,
158
167
  },
159
168
  { webhookUrl: 'https://example.com/webhook' },
160
169
  );
@@ -543,6 +552,7 @@ Video model support notes:
543
552
  - `createVideoFromImageList` can render per-scene footer QR cards by setting `add_footer_animation: true` and providing one `footer_metadata` item per image scene.
544
553
  - `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.
545
554
  - `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.
555
+ - `cloneVideo` creates a deep copy of a completed session and queues only the final video render so the clone receives a new rendered video path and URL. It does not charge credits.
546
556
  - 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.
547
557
  - Completed video status, latest-version, and completed-session list responses expose `has_subtitles` and `result_language` when the session metadata is available.
548
558
  - `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.
@@ -550,7 +560,7 @@ Video model support notes:
550
560
 
551
561
  Upcoming `/v2` omni route adapters:
552
562
  - `/v2` is additive; `/v1` is not deprecated.
553
- - `createV2VideoFromText`, `createV2VideoFromImageList`, `updateV2VideoOutroImage`, `updateV2VideoFooterImage`, `addV2VideoOutroImage`, `getV2Status`, `getV2Credits`, `listV2Requests`, and `createV2Session` call the new omni route surface.
563
+ - `createV2VideoFromText`, `createV2VideoFromImageList`, `translateV2Video`, `cloneV2Video`, `updateV2VideoOutroImage`, `updateV2VideoFooterImage`, `addV2VideoOutroImage`, `getV2Status`, `getV2Credits`, `listV2Requests`, and `createV2Session` call the new omni route surface.
554
564
  - Programmatic user billing helpers include `createV2UserRechargeCredits`, `refreshV2UserToken`, `createV2UserAppKey`, `refreshV2UserAppKey`, `getV2UserCredits`, `getV2UserUsageLogs`, and `getV2UserPaymentStatus`.
555
565
  - 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.
556
566
 
@@ -571,6 +581,18 @@ const v2ExternalVideo = await platform.createV2VideoFromImageList(
571
581
  { externalUser },
572
582
  );
573
583
 
584
+ const v2Translated = await platform.translateV2Video({
585
+ videoSessionId: v2Video.data.request_id!,
586
+ language: 'es',
587
+ enable_subtitles: false,
588
+ translate_outro: true,
589
+ translate_footer: true,
590
+ });
591
+
592
+ const v2Clone = await platform.cloneV2Video({
593
+ videoSessionId: v2Video.data.request_id!,
594
+ });
595
+
574
596
  const v2Status = await platform.getV2Status(v2Video.data.request_id!);
575
597
  console.log(v2Status.data.status);
576
598
  ```
package/dist/index.d.ts CHANGED
@@ -219,10 +219,14 @@ export interface TranslateVideoInput {
219
219
  langauge?: string;
220
220
  langauge_code?: string;
221
221
  langaugeCode?: string;
222
- outro_image_url?: string;
223
- outroImageUrl?: string;
224
- new_outro_image_url?: string;
225
- newOutroImageUrl?: string;
222
+ enable_subtitles?: boolean;
223
+ enableSubtitles?: boolean;
224
+ add_subtitles?: boolean;
225
+ addSubtitles?: boolean;
226
+ translate_outro?: boolean;
227
+ translateOutro?: boolean;
228
+ translate_footer?: boolean;
229
+ translateFooter?: boolean;
226
230
  [key: string]: unknown;
227
231
  }
228
232
  export interface TranslateVideoResponse {
@@ -233,6 +237,26 @@ export interface TranslateVideoResponse {
233
237
  remainingCredits?: number | null;
234
238
  [key: string]: unknown;
235
239
  }
240
+ export interface CloneVideoInput {
241
+ videoSessionId?: string;
242
+ video_session_id?: string;
243
+ videoSessionID?: string;
244
+ session_id?: string;
245
+ sessionId?: string;
246
+ sessionID?: string;
247
+ request_id?: string;
248
+ requestId?: string;
249
+ source_session_id?: string;
250
+ sourceSessionId?: string;
251
+ source_request_id?: string;
252
+ sourceRequestId?: string;
253
+ [key: string]: unknown;
254
+ }
255
+ export interface CloneVideoResponse extends GlobalStatusResponse {
256
+ sessionID?: string;
257
+ creditsCharged?: number;
258
+ remainingCredits?: number | null;
259
+ }
236
260
  export interface UpdateVideoOutroImageInput {
237
261
  videoSessionId?: string;
238
262
  video_session_id?: string;
@@ -1717,6 +1741,8 @@ export declare class SamsarClient {
1717
1741
  }): Promise<SamsarResult<CreateLoginTokenResponse | ExternalCreateLoginTokenResponse>>;
1718
1742
  createV2VideoFromText(input: CreateVideoFromTextInput, options?: V2RequestOptions): Promise<SamsarResult<CreateVideoResponse | ExternalRequestResponse>>;
1719
1743
  createV2VideoFromImageList(input: CreateVideoFromImageListInput, options?: V2RequestOptions): Promise<SamsarResult<CreateVideoFromImageListResponse | ExternalRequestResponse>>;
1744
+ translateV2Video(input: TranslateVideoInput, options?: V2RequestOptions): Promise<SamsarResult<TranslateVideoResponse | ExternalRequestResponse>>;
1745
+ cloneV2Video(input: CloneVideoInput, options?: V2RequestOptions): Promise<SamsarResult<CloneVideoResponse>>;
1720
1746
  uploadV2ImageData(imageData: string[], options?: V2RequestOptions): Promise<SamsarResult<{
1721
1747
  image_urls: string[];
1722
1748
  }>>;
@@ -1802,6 +1828,13 @@ export declare class SamsarClient {
1802
1828
  translateVideo(input: TranslateVideoInput, options?: {
1803
1829
  webhookUrl?: string;
1804
1830
  } & SamsarRequestOptions): Promise<SamsarResult<TranslateVideoResponse>>;
1831
+ /**
1832
+ * Deep-clone a completed video session and queue one final render for the new session.
1833
+ * The clone copies session assets, then regenerates only the final rendered video URL.
1834
+ */
1835
+ cloneVideo(input: CloneVideoInput, options?: {
1836
+ webhookUrl?: string;
1837
+ } & SamsarRequestOptions): Promise<SamsarResult<CloneVideoResponse>>;
1805
1838
  /**
1806
1839
  * Join multiple existing video sessions into a single session by appending their timelines.
1807
1840
  * Creates a new session_id and queues transcription + frame + video generation.
package/dist/index.js CHANGED
@@ -126,6 +126,72 @@ function normalizeCreateVideoFromImageListInput(input) {
126
126
  }
127
127
  return normalized;
128
128
  }
129
+ function normalizeTranslateVideoInput(input, context = 'translateVideo') {
130
+ const raw = input;
131
+ const videoSessionId = raw.videoSessionId ??
132
+ raw.video_session_id ??
133
+ raw.videoSessionID ??
134
+ raw.session_id ??
135
+ raw.sessionId ??
136
+ raw.sessionID ??
137
+ raw.request_id ??
138
+ raw.requestId;
139
+ const language = raw.language ??
140
+ raw.language_code ??
141
+ raw.languageCode ??
142
+ raw.langauge ??
143
+ raw.langauge_code ??
144
+ raw.langaugeCode ??
145
+ raw.languageString;
146
+ const enableSubtitles = resolveAliasedInputValue(raw, ['enable_subtitles', 'enableSubtitles', 'add_subtitles', 'addSubtitles'], 'enable_subtitles') ?? false;
147
+ const translateOutro = resolveAliasedInputValue(raw, ['translate_outro', 'translateOutro'], 'translate_outro') ?? true;
148
+ const translateFooter = resolveAliasedInputValue(raw, ['translate_footer', 'translateFooter'], 'translate_footer') ?? true;
149
+ if (!videoSessionId) {
150
+ throw new Error(`videoSessionId is required for ${context}`);
151
+ }
152
+ if (!language) {
153
+ throw new Error(`language is required for ${context}`);
154
+ }
155
+ assertOptionalBoolean(enableSubtitles, 'enable_subtitles', context);
156
+ assertOptionalBoolean(translateOutro, 'translate_outro', context);
157
+ assertOptionalBoolean(translateFooter, 'translate_footer', context);
158
+ const normalizedInput = { ...input };
159
+ delete normalizedInput.outro_image_url;
160
+ delete normalizedInput.outroImageUrl;
161
+ delete normalizedInput.outroImageURL;
162
+ delete normalizedInput.new_outro_image_url;
163
+ delete normalizedInput.newOutroImageUrl;
164
+ return {
165
+ ...normalizedInput,
166
+ videoSessionId: String(videoSessionId),
167
+ language: String(language),
168
+ enable_subtitles: enableSubtitles,
169
+ translate_outro: translateOutro,
170
+ translate_footer: translateFooter,
171
+ };
172
+ }
173
+ function normalizeCloneVideoInput(input, context = 'cloneVideo') {
174
+ const raw = input;
175
+ const videoSessionId = raw.videoSessionId ??
176
+ raw.video_session_id ??
177
+ raw.videoSessionID ??
178
+ raw.session_id ??
179
+ raw.sessionId ??
180
+ raw.sessionID ??
181
+ raw.request_id ??
182
+ raw.requestId ??
183
+ raw.source_session_id ??
184
+ raw.sourceSessionId ??
185
+ raw.source_request_id ??
186
+ raw.sourceRequestId;
187
+ if (!videoSessionId) {
188
+ throw new Error(`videoSessionId is required for ${context}`);
189
+ }
190
+ return {
191
+ ...input,
192
+ videoSessionId: String(videoSessionId),
193
+ };
194
+ }
129
195
  function normalizeUpdateVideoOutroImageInput(input, context = 'updateVideoOutroImage') {
130
196
  const raw = input;
131
197
  const videoSessionId = raw.videoSessionId ??
@@ -490,6 +556,20 @@ export class SamsarClient {
490
556
  webhookUrl: options?.webhookUrl,
491
557
  }, options);
492
558
  }
559
+ async translateV2Video(input, options) {
560
+ const normalizedInput = normalizeTranslateVideoInput(input, 'translateV2Video');
561
+ return this.postV2('translate_video', {
562
+ input: normalizedInput,
563
+ webhookUrl: options?.webhookUrl,
564
+ }, options);
565
+ }
566
+ async cloneV2Video(input, options) {
567
+ const normalizedInput = normalizeCloneVideoInput(input, 'cloneV2Video');
568
+ return this.postV2('video/clone', {
569
+ input: normalizedInput,
570
+ webhookUrl: options?.webhookUrl,
571
+ }, options);
572
+ }
493
573
  async uploadV2ImageData(imageData, options) {
494
574
  if (!Array.isArray(imageData) || imageData.length === 0) {
495
575
  throw new Error('imageData must be a non-empty array of data URLs');
@@ -703,33 +783,10 @@ export class SamsarClient {
703
783
  * Creates a new session_id and queues generation steps for lip sync + transcription + video render.
704
784
  */
705
785
  async translateVideo(input, options) {
706
- const raw = input;
707
- const videoSessionId = raw.videoSessionId ??
708
- raw.video_session_id ??
709
- raw.videoSessionID ??
710
- raw.session_id ??
711
- raw.sessionId ??
712
- raw.sessionID ??
713
- raw.request_id ??
714
- raw.requestId;
715
- const language = raw.language ??
716
- raw.language_code ??
717
- raw.languageCode ??
718
- raw.langauge ??
719
- raw.langauge_code ??
720
- raw.langaugeCode ??
721
- raw.languageString;
722
- if (!videoSessionId) {
723
- throw new Error('videoSessionId is required for translateVideo');
724
- }
725
- if (!language) {
726
- throw new Error('language is required for translateVideo');
727
- }
786
+ const normalizedInput = normalizeTranslateVideoInput(input, 'translateVideo');
728
787
  const body = {
729
788
  input: {
730
- ...input,
731
- videoSessionId: String(videoSessionId),
732
- language: String(language),
789
+ ...normalizedInput,
733
790
  },
734
791
  webhookUrl: options?.webhookUrl,
735
792
  };
@@ -754,6 +811,39 @@ export class SamsarClient {
754
811
  }
755
812
  return response;
756
813
  }
814
+ /**
815
+ * Deep-clone a completed video session and queue one final render for the new session.
816
+ * The clone copies session assets, then regenerates only the final rendered video URL.
817
+ */
818
+ async cloneVideo(input, options) {
819
+ const normalizedInput = normalizeCloneVideoInput(input, 'cloneVideo');
820
+ const body = {
821
+ input: {
822
+ ...normalizedInput,
823
+ },
824
+ webhookUrl: options?.webhookUrl,
825
+ };
826
+ const response = await this.post('video/clone', body, options);
827
+ const data = response.data;
828
+ if (data && typeof data === 'object') {
829
+ const sessionId = typeof data.sessionID === 'string'
830
+ ? data.sessionID
831
+ : typeof data.session_id === 'string'
832
+ ? data.session_id
833
+ : typeof data.request_id === 'string'
834
+ ? data.request_id
835
+ : undefined;
836
+ const normalizedSessionId = sessionId ? String(sessionId) : undefined;
837
+ const normalizedData = {
838
+ ...data,
839
+ sessionID: data.sessionID ?? normalizedSessionId ?? '',
840
+ session_id: data.session_id ?? normalizedSessionId ?? null,
841
+ request_id: data.request_id ?? normalizedSessionId ?? null,
842
+ };
843
+ return { ...response, data: normalizedData };
844
+ }
845
+ return response;
846
+ }
757
847
  /**
758
848
  * Join multiple existing video sessions into a single session by appending their timelines.
759
849
  * Creates a new session_id and queues transcription + frame + video generation.
@@ -1861,7 +1951,10 @@ export class SamsarClient {
1861
1951
  body: parsedBody,
1862
1952
  });
1863
1953
  }
1864
- throw new SamsarRequestError(`Request to ${url} failed with status ${response.status}`, {
1954
+ const responseMessage = getResponseErrorMessage(parsedBody);
1955
+ throw new SamsarRequestError(responseMessage
1956
+ ? `Request to ${url} failed with status ${response.status}: ${responseMessage}`
1957
+ : `Request to ${url} failed with status ${response.status}`, {
1865
1958
  status: response.status,
1866
1959
  body: parsedBody,
1867
1960
  headers: headerRecord,
@@ -1943,6 +2036,25 @@ function parseMaybeJson(value) {
1943
2036
  return value;
1944
2037
  }
1945
2038
  }
2039
+ function getResponseErrorMessage(body) {
2040
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
2041
+ return typeof body === 'string' && body.trim() ? body.trim() : null;
2042
+ }
2043
+ const record = body;
2044
+ for (const key of ['message', 'error', 'detail']) {
2045
+ const value = record[key];
2046
+ if (typeof value === 'string' && value.trim()) {
2047
+ return value.trim();
2048
+ }
2049
+ }
2050
+ if (record.error && typeof record.error === 'object' && !Array.isArray(record.error)) {
2051
+ const nestedMessage = record.error.message;
2052
+ if (typeof nestedMessage === 'string' && nestedMessage.trim()) {
2053
+ return nestedMessage.trim();
2054
+ }
2055
+ }
2056
+ return null;
2057
+ }
1946
2058
  function headersToObject(headers) {
1947
2059
  const result = {};
1948
2060
  headers.forEach((value, key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "samsar-js",
3
- "version": "0.48.24",
3
+ "version": "0.48.26",
4
4
  "description": "TypeScript client for the Samsar Processor API routes.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",