@rohitaryal/whisk-api 3.2.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,11 +16,12 @@ bun i -g @rohitaryal/whisk-api
16
16
  ## Features
17
17
 
18
18
  1. Text to Image using `IMAGEN_3_5`
19
- 2. Image to Video (Animation) using `VEO`
20
- 3. Image Refinement (Editing/Inpainting using NanoBanana)
21
- 4. Image to Text
19
+ 2. Image to Video (Animation) using `VEO_3_1`
20
+ 3. Image Refinement (Editing/Inpainting)
21
+ 4. Image to Text (Caption Generation)
22
22
  5. Project & Media Management
23
- 6. Command line support
23
+ 6. Upload & Reference Images
24
+ 7. Command line support
24
25
 
25
26
  ## Usage
26
27
 
@@ -62,6 +63,12 @@ Make sure you have:
62
63
  > - PowerShell: `"$env:COOKIE"`
63
64
  > - Command Prompt: `"%COOKIE%"`
64
65
 
66
+ - Creating a new project
67
+
68
+ ```bash
69
+ whisk project --name "My Project" --cookie "$COOKIE"
70
+ ```
71
+
65
72
  - Generating image with prompt
66
73
 
67
74
  ```bash
@@ -95,55 +102,164 @@ Make sure you have:
95
102
  whisk caption --file /path/to/img.webp --count 3 --cookie "$COOKIE"
96
103
  ```
97
104
 
105
+ - Fetching an existing media by ID
106
+
107
+ ```bash
108
+ whisk fetch "__MEDIA__ID__HERE__" --cookie "$COOKIE"
109
+ ```
110
+
111
+ - Uploading an image as reference
112
+
113
+ ```bash
114
+ whisk upload /path/to/image.jpg --category SUBJECT --project "__PROJECT__ID__" --cookie "$COOKIE"
115
+ ```
116
+
98
117
  - Deleting media from the cloud
99
118
 
100
119
  ```bash
101
120
  whisk delete "__MEDIA__ID__HERE__" --cookie "$COOKIE"
102
121
  ```
103
122
 
104
- Full generation help:
123
+ Full CLI commands help:
105
124
 
106
125
  ```text
107
- whisk generate <options>
126
+ whisk <cmd> [args]
127
+
128
+ Commands:
129
+ whisk project Generate a new project
130
+ whisk generate Generate new images using a temporary project
131
+ whisk animate <mediaId> Animate an existing landscape image into a video
132
+ whisk refine <mediaId> Edit/Refine an existing image
133
+ whisk caption Generate captions for a local image file
134
+ whisk fetch <mediaId> Download an existing generated media by ID
135
+ whisk upload <file> Upload an image to be used as reference later
136
+ whisk delete <mediaId> Delete a generated media from the cloud
108
137
 
109
138
  Options:
110
- --version Show version number
111
- -h, --help Show help
112
- -p, --prompt Description of the image
113
- -m, --model Image generation model (Default: IMAGEN_3_5)
114
- -a, --aspect Aspect ratio (SQUARE, PORTRAIT, LANDSCAPE)
115
- -s, --seed Seed value (0 for random)
116
- -d, --dir Output directory
117
- -c, --cookie Google account cookie
139
+ -c, --cookie Google account cookie [required]
140
+ -h, --help Show help
141
+ --version Show version number
118
142
  ```
119
143
 
120
- Full animation help:
144
+ <details>
145
+ <summary>Project Command</summary>
146
+
147
+ ```text
148
+ whisk project
149
+
150
+ Options:
151
+ --name Project name [default: "Whisk-CLI project"]
152
+ -c, --cookie Google account cookie [required]
153
+ ```
154
+ </details>
155
+
156
+ <details>
157
+ <summary>Generate Command</summary>
158
+
159
+ ```text
160
+ whisk generate
161
+
162
+ Options:
163
+ -p, --prompt Description of the image [required]
164
+ -m, --model Image generation model [default: "IMAGEN_3_5"]
165
+ -a, --aspect Aspect ratio (SQUARE, PORTRAIT, LANDSCAPE) [default: "LANDSCAPE"]
166
+ -s, --seed Seed value (0 for random) [default: 0]
167
+ -d, --dir Output directory [default: "./output"]
168
+ -c, --cookie Google account cookie [required]
169
+ ```
170
+ </details>
171
+
172
+ <details>
173
+ <summary>Animate Command</summary>
121
174
 
122
175
  ```text
123
176
  whisk animate <mediaId>
124
177
 
125
178
  Positionals:
126
- mediaId The ID of the image to animate
179
+ mediaId The ID of the image to animate [required]
180
+
181
+ Options:
182
+ -s, --script Prompt/Script for the video animation [required]
183
+ -m, --model Video generation model [default: "VEO_3_1"]
184
+ -d, --dir Output directory [default: "./output"]
185
+ -c, --cookie Google account cookie [required]
186
+ ```
187
+ </details>
188
+
189
+ <details>
190
+ <summary>Refine Command</summary>
191
+
192
+ ```text
193
+ whisk refine <mediaId>
194
+
195
+ Positionals:
196
+ mediaId The ID of the image to refine [required]
197
+
198
+ Options:
199
+ -p, --prompt Instruction for editing (e.g., 'Make it snowy') [required]
200
+ -d, --dir Output directory [default: "./output"]
201
+ -c, --cookie Google account cookie [required]
202
+ ```
203
+ </details>
204
+
205
+ <details>
206
+ <summary>Caption Command</summary>
207
+
208
+ ```text
209
+ whisk caption
127
210
 
128
211
  Options:
129
- -s, --script Prompt/Script for the video animation
130
- -m, --model Video generation model (Default: VEO_FAST_3_1)
131
- -d, --dir Output directory
132
- -c, --cookie Google account cookie
212
+ -f, --file Path to local image file [required]
213
+ -n, --count Number of captions [default: 1]
214
+ -c, --cookie Google account cookie [required]
133
215
  ```
216
+ </details>
134
217
 
135
- Full fetch help:
218
+ <details>
219
+ <summary>Fetch Command</summary>
136
220
 
137
221
  ```text
138
222
  whisk fetch <mediaId>
139
223
 
140
224
  Positionals:
141
- mediaId Unique ID of generated media
225
+ mediaId Unique ID of generated media [required]
142
226
 
143
227
  Options:
144
- -d, --dir Output directory
145
- -c, --cookie Google account cookie
228
+ -d, --dir Output directory [default: "./output"]
229
+ -c, --cookie Google account cookie [required]
146
230
  ```
231
+ </details>
232
+
233
+ <details>
234
+ <summary>Upload Command</summary>
235
+
236
+ ```text
237
+ whisk upload <file>
238
+
239
+ Positionals:
240
+ file Path to local image file [required]
241
+
242
+ Options:
243
+ --category, -ca Category of reference [required]
244
+ Choices: SUBJECT, SCENE, STYLE
245
+ --project, -pr Project/Workflow ID [required]
246
+ -c, --cookie Google account cookie [required]
247
+ ```
248
+ </details>
249
+
250
+ <details>
251
+ <summary>Delete Command</summary>
252
+
253
+ ```text
254
+ whisk delete <mediaId>
255
+
256
+ Positionals:
257
+ mediaId Unique ID of generated media to delete [required]
258
+
259
+ Options:
260
+ -c, --cookie Google account cookie [required]
261
+ ```
262
+ </details>
147
263
 
148
264
  </details>
149
265
 
@@ -186,7 +302,7 @@ Options:
186
302
  const refinedImage = await baseImage.refine("Make it raining neon rain");
187
303
 
188
304
  // 3. Animate (Video)
189
- const video = await refinedImage.animate("Camera flies through the streets", "VEO_FAST_3_1");
305
+ const video = await refinedImage.animate("Camera flies through the streets", "VEO_3_1_I2V_12STEP");
190
306
 
191
307
  video.save("./videos");
192
308
  ```
package/dist/Cli.js CHANGED
@@ -3,11 +3,33 @@ import yargs from "yargs";
3
3
  import { hideBin } from 'yargs/helpers';
4
4
  import { Whisk } from "./Whisk.js";
5
5
  import { imageToBase64 } from "./Utils.js";
6
- import { ImageAspectRatio, ImageGenerationModel, VideoGenerationModel } from "./Constants.js";
6
+ import { ImageAspectRatio, ImageGenerationModel, MediaCategory, VideoGenerationModel } from "./Constants.js";
7
7
  const y = yargs(hideBin(process.argv));
8
8
  await y
9
9
  .scriptName("whisk")
10
10
  .usage('$0 <cmd> [args]')
11
+ .option("cookie", {
12
+ alias: "c",
13
+ describe: "Google account cookie",
14
+ type: "string",
15
+ demandOption: true,
16
+ })
17
+ .command("project", "Generate a new project", (yargs) => {
18
+ return yargs
19
+ .option("name", {
20
+ describe: "Project name",
21
+ demandOption: false,
22
+ type: "string",
23
+ default: "Whisk-CLI project"
24
+ });
25
+ }, async (argv) => {
26
+ const whisk = new Whisk(argv.cookie);
27
+ await whisk.account.refresh();
28
+ console.log(whisk.account.toString());
29
+ console.log("[*] Creating new project...");
30
+ const project = await whisk.newProject(argv.name);
31
+ console.log("[+] Project ID:", project.projectId);
32
+ })
11
33
  .command("generate", "Generate new images using a temporary project", (yargs) => {
12
34
  return yargs
13
35
  .option("prompt", {
@@ -41,12 +63,6 @@ await y
41
63
  describe: "Output directory",
42
64
  type: "string",
43
65
  default: "./output",
44
- })
45
- .option("cookie", {
46
- alias: "c",
47
- describe: "Google account cookie",
48
- type: "string",
49
- demandOption: true,
50
66
  });
51
67
  }, async (argv) => {
52
68
  const whisk = new Whisk(argv.cookie);
@@ -91,7 +107,7 @@ await y
91
107
  alias: "m",
92
108
  describe: "Video generation model",
93
109
  type: "string",
94
- default: "VEO_FAST_3_1",
110
+ default: "VEO_3_1",
95
111
  choices: Object.keys(VideoGenerationModel)
96
112
  })
97
113
  .option("dir", {
@@ -99,12 +115,6 @@ await y
99
115
  describe: "Output directory",
100
116
  type: "string",
101
117
  default: "./output",
102
- })
103
- .option("cookie", {
104
- alias: "c",
105
- describe: "Google account cookie",
106
- type: "string",
107
- demandOption: true,
108
118
  });
109
119
  }, async (argv) => {
110
120
  const whisk = new Whisk(argv.cookie);
@@ -141,12 +151,6 @@ await y
141
151
  describe: "Output directory",
142
152
  type: "string",
143
153
  default: "./output",
144
- })
145
- .option("cookie", {
146
- alias: "c",
147
- describe: "Google account cookie",
148
- type: "string",
149
- demandOption: true,
150
154
  });
151
155
  }, async (argv) => {
152
156
  const whisk = new Whisk(argv.cookie);
@@ -178,12 +182,6 @@ await y
178
182
  describe: "Number of captions",
179
183
  type: "number",
180
184
  default: 1
181
- })
182
- .option("cookie", {
183
- alias: "c",
184
- describe: "Google account cookie",
185
- type: "string",
186
- demandOption: true,
187
185
  });
188
186
  }, async (argv) => {
189
187
  const whisk = new Whisk(argv.cookie);
@@ -216,12 +214,6 @@ await y
216
214
  describe: "Output directory",
217
215
  type: "string",
218
216
  default: "./output",
219
- })
220
- .option("cookie", {
221
- alias: "c",
222
- describe: "Google account cookie",
223
- type: "string",
224
- demandOption: true,
225
217
  });
226
218
  }, async (argv) => {
227
219
  const whisk = new Whisk(argv.cookie);
@@ -237,16 +229,43 @@ await y
237
229
  console.error("[!] Fetch failed:", error);
238
230
  }
239
231
  })
240
- .command("delete <mediaId>", "Delete a generated media from the cloud", (yargs) => {
232
+ .command("upload <file>", "Upload an image to be used as reference later. References can't be used currently", (yargs) => {
241
233
  return yargs
242
- .positional("mediaId", {
243
- describe: "Unique ID of generated media to delete",
234
+ .positional("file", {
235
+ describe: "Path to local image file",
236
+ type: "string",
237
+ demandOption: true
238
+ })
239
+ .option("category", {
240
+ alias: "ca",
241
+ describe: "Category of reference",
244
242
  type: "string",
245
243
  demandOption: true,
244
+ choices: Object.keys(MediaCategory)
246
245
  })
247
- .option("cookie", {
248
- alias: "c",
249
- describe: "Google account cookie",
246
+ .option("project", {
247
+ alias: "pr",
248
+ describe: "Project/Workflow ID",
249
+ type: "string",
250
+ demandOption: true
251
+ });
252
+ }, async (argv) => {
253
+ const whisk = new Whisk(argv.cookie);
254
+ await whisk.account.refresh();
255
+ console.log(whisk.account.toString());
256
+ console.log("[*] Generating caption for image...");
257
+ const base64 = await imageToBase64(argv.file);
258
+ // Split at the comma to get the raw base64 string
259
+ const rawBase64 = base64.split(",")[1];
260
+ const captionedImage = await Whisk.generateCaption(rawBase64, whisk.account);
261
+ console.log("[*] Uploading image...");
262
+ let mediaId = await Whisk.uploadImage(rawBase64, captionedImage[0], MediaCategory[argv.category], argv.project, whisk.account);
263
+ console.log("[+] ID:", mediaId);
264
+ })
265
+ .command("delete <mediaId>", "Delete a generated media from the cloud", (yargs) => {
266
+ return yargs
267
+ .positional("mediaId", {
268
+ describe: "Unique ID of generated media to delete",
250
269
  type: "string",
251
270
  demandOption: true,
252
271
  });
@@ -33,5 +33,9 @@ export declare const ImageRefinementModel: Readonly<{
33
33
  }>;
34
34
  export declare const VideoGenerationModel: Readonly<{
35
35
  readonly VEO_3_1: "VEO_3_1_I2V_12STEP";
36
- readonly VEO_FAST_3_1: "veo_3_1_i2v_s_fast";
36
+ }>;
37
+ export declare const MediaCategory: Readonly<{
38
+ readonly SUBJECT: "MEDIA_CATEGORY_SUBJECT";
39
+ readonly SCENE: "MEDIA_CATEGORY_SCENE";
40
+ readonly STYLE: "MEDIA_CATEGORY_STYLE";
37
41
  }>;
package/dist/Constants.js CHANGED
@@ -33,5 +33,11 @@ export const ImageRefinementModel = Object.freeze({
33
33
  });
34
34
  export const VideoGenerationModel = Object.freeze({
35
35
  VEO_3_1: "VEO_3_1_I2V_12STEP",
36
- VEO_FAST_3_1: "veo_3_1_i2v_s_fast",
36
+ // https://github.com/rohitaryal/whisk-api/issues/13
37
+ // VEO_FAST_3_1: "veo_3_1_i2v_s_fast",
38
+ });
39
+ export const MediaCategory = Object.freeze({
40
+ SUBJECT: "MEDIA_CATEGORY_SUBJECT",
41
+ SCENE: "MEDIA_CATEGORY_SCENE",
42
+ STYLE: "MEDIA_CATEGORY_STYLE",
37
43
  });
package/dist/Media.js CHANGED
@@ -150,21 +150,23 @@ export class Media {
150
150
  // Await for 2 second before each request
151
151
  await new Promise(resolve => setTimeout(resolve, 2000));
152
152
  if (videoResults.status === "MEDIA_GENERATION_STATUS_SUCCESSFUL") {
153
- const video = videoResults.operation.metadata.video;
153
+ const video = videoResults.operations[0].operation.metadata.video;
154
154
  return new Media({
155
155
  seed: video.seed,
156
156
  prompt: video.prompt,
157
157
  workflowId: this.workflowId,
158
- encodedMedia: videoResults.rawBytes,
159
- mediaGenerationId: videoResults.mediaGenerationId,
158
+ encodedMedia: videoResults.operations[0].rawBytes,
159
+ mediaGenerationId: videoResults.operations[0].mediaGenerationId,
160
160
  aspectRatio: video.aspectRatio,
161
161
  mediaType: "VIDEO",
162
162
  model: video.model,
163
163
  account: this.account,
164
164
  });
165
165
  }
166
- if (i >= 20) {
167
- throw new Error("failed to generate video: " + videoResults);
166
+ // Wait for few more time
167
+ // https://github.com/rohitaryal/whisk-api/issues/4
168
+ if (i >= 60) {
169
+ throw new Error("failed to generate video: " + JSON.stringify(videoResults));
168
170
  }
169
171
  }
170
172
  }
package/dist/Project.d.ts CHANGED
@@ -1,13 +1,40 @@
1
1
  import { Media } from "./Media.js";
2
- import type { PromptConfig } from "./Types.js";
2
+ import type { ImageInput, MediaReference, PromptConfig } from "./Types.js";
3
3
  import { Account } from "./Whisk.js";
4
4
  export declare class Project {
5
5
  readonly account: Account;
6
6
  readonly projectId: string;
7
- readonly subjects: Media[];
8
- readonly scenes: Media[];
9
- readonly styles: Media[];
7
+ readonly subjects: MediaReference[];
8
+ readonly scenes: MediaReference[];
9
+ readonly styles: MediaReference[];
10
10
  constructor(projectId: string, account: Account);
11
+ /**
12
+ * Uploads a custom image and adds it as a subject reference
13
+ *
14
+ * @param input Image as { file: string }, { url: string }, or { base64: string }
15
+ */
16
+ addSubject(input: ImageInput): Promise<MediaReference>;
17
+ /**
18
+ * Uploads a custom image and adds it as a scene reference
19
+ *
20
+ * @param input Image as { file: string }, { url: string }, or { base64: string }
21
+ */
22
+ addScene(input: ImageInput): Promise<MediaReference>;
23
+ /**
24
+ * Uploads a custom image and adds it as a style reference
25
+ *
26
+ * @param input Image as { file: string }, { url: string }, or { base64: string }
27
+ */
28
+ addStyle(input: ImageInput): Promise<MediaReference>;
29
+ addSubjectById(mediaGenerationId: string, prompt: string): void;
30
+ addSceneById(mediaGenerationId: string, prompt: string): void;
31
+ addStyleById(mediaGenerationId: string, prompt: string): void;
32
+ removeSubject(mediaGenerationId: string): MediaReference;
33
+ removeScene(mediaGenerationId: string): MediaReference;
34
+ removeStyle(mediaGenerationId: string): MediaReference;
35
+ private addById;
36
+ private removeById;
37
+ private addReference;
11
38
  generateImage(input: string | PromptConfig): Promise<Media>;
12
39
  /**
13
40
  * Generate image but with subject, scene, style attached
package/dist/Project.js CHANGED
@@ -1,7 +1,7 @@
1
- import { ImageGenerationModel } from "./Constants.js";
1
+ import { ImageGenerationModel, MediaCategory } from "./Constants.js";
2
2
  import { Media } from "./Media.js";
3
- import { request } from "./Utils.js";
4
- import { Account } from "./Whisk.js";
3
+ import { request, resolveImageInput } from "./Utils.js";
4
+ import { Account, Whisk } from "./Whisk.js";
5
5
  export class Project {
6
6
  account;
7
7
  projectId;
@@ -21,6 +21,98 @@ export class Project {
21
21
  this.scenes = [];
22
22
  this.styles = [];
23
23
  }
24
+ /**
25
+ * Uploads a custom image and adds it as a subject reference
26
+ *
27
+ * @param input Image as { file: string }, { url: string }, or { base64: string }
28
+ */
29
+ async addSubject(input) {
30
+ return this.addReference(input, MediaCategory.SUBJECT, this.subjects);
31
+ }
32
+ /**
33
+ * Uploads a custom image and adds it as a scene reference
34
+ *
35
+ * @param input Image as { file: string }, { url: string }, or { base64: string }
36
+ */
37
+ async addScene(input) {
38
+ return this.addReference(input, MediaCategory.SCENE, this.scenes);
39
+ }
40
+ /**
41
+ * Uploads a custom image and adds it as a style reference
42
+ *
43
+ * @param input Image as { file: string }, { url: string }, or { base64: string }
44
+ */
45
+ async addStyle(input) {
46
+ return this.addReference(input, MediaCategory.STYLE, this.styles);
47
+ }
48
+ addSubjectById(mediaGenerationId, prompt) {
49
+ this.addById(mediaGenerationId, prompt, this.subjects);
50
+ }
51
+ addSceneById(mediaGenerationId, prompt) {
52
+ this.addById(mediaGenerationId, prompt, this.scenes);
53
+ }
54
+ addStyleById(mediaGenerationId, prompt) {
55
+ this.addById(mediaGenerationId, prompt, this.styles);
56
+ }
57
+ removeSubject(mediaGenerationId) {
58
+ return this.removeById(mediaGenerationId, this.subjects);
59
+ }
60
+ removeScene(mediaGenerationId) {
61
+ return this.removeById(mediaGenerationId, this.scenes);
62
+ }
63
+ removeStyle(mediaGenerationId) {
64
+ return this.removeById(mediaGenerationId, this.styles);
65
+ }
66
+ addById(mediaGenerationId, prompt, target) {
67
+ if (typeof mediaGenerationId !== "string" || !mediaGenerationId.trim()) {
68
+ throw new Error("media generation id is required");
69
+ }
70
+ if (typeof prompt !== "string" || !prompt.trim()) {
71
+ throw new Error("prompt is required");
72
+ }
73
+ if (target.some(ref => ref.mediaGenerationId === mediaGenerationId)) {
74
+ throw new Error(`'${mediaGenerationId}': reference already exists`);
75
+ }
76
+ target.push({ prompt, mediaGenerationId });
77
+ }
78
+ removeById(mediaGenerationId, target) {
79
+ if (typeof mediaGenerationId !== "string" || !mediaGenerationId.trim()) {
80
+ throw new Error("media generation id is required");
81
+ }
82
+ const index = target.findIndex(ref => ref.mediaGenerationId === mediaGenerationId);
83
+ if (index === -1) {
84
+ throw new Error(`'${mediaGenerationId}': reference not found`);
85
+ }
86
+ return target.splice(index, 1)[0];
87
+ }
88
+ async addReference(input, category, target) {
89
+ const rawBytes = await resolveImageInput(input);
90
+ if (!(rawBytes?.trim?.())) {
91
+ throw new Error("image data is required");
92
+ }
93
+ const captions = await request("https://labs.google/fx/api/trpc/backbone.captionImage", {
94
+ headers: { cookie: this.account.getCookie() },
95
+ body: JSON.stringify({
96
+ "json": {
97
+ "clientContext": {
98
+ "workflowId": this.projectId
99
+ },
100
+ "captionInput": {
101
+ "candidatesCount": 1,
102
+ "mediaInput": {
103
+ "mediaCategory": category,
104
+ "rawBytes": rawBytes
105
+ }
106
+ }
107
+ }
108
+ })
109
+ });
110
+ const caption = captions.candidates[0].output;
111
+ const uploadMediaGenerationId = await Whisk.uploadImage(rawBytes, caption, category, this.projectId, this.account);
112
+ const ref = { prompt: caption, mediaGenerationId: uploadMediaGenerationId };
113
+ target.push(ref);
114
+ return ref;
115
+ }
24
116
  async generateImage(input) {
25
117
  if (typeof input === "string") {
26
118
  input = {
package/dist/Types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Account } from "./Whisk.js";
2
- import { ImageAspectRatio, VideoAspectRatio, ImageExtension, ImageGenerationModel, VideoGenerationModel, ImageRefinementModel } from "./Constants.js";
2
+ import { ImageAspectRatio, VideoAspectRatio, ImageExtension, ImageGenerationModel, VideoGenerationModel, ImageRefinementModel, MediaCategory } from "./Constants.js";
3
3
  export interface MediaConfig {
4
4
  seed: number;
5
5
  prompt: string;
@@ -24,3 +24,15 @@ export type ImageExtensionTypes = typeof ImageExtension[keyof typeof ImageExtens
24
24
  export type ImageGenerationModelType = typeof ImageGenerationModel[keyof typeof ImageGenerationModel];
25
25
  export type VideoGenerationModelType = typeof VideoGenerationModel[keyof typeof VideoGenerationModel];
26
26
  export type ImageRefinementModelType = typeof ImageRefinementModel[keyof typeof ImageRefinementModel];
27
+ export type MediaCategoryType = typeof MediaCategory[keyof typeof MediaCategory];
28
+ export interface MediaReference {
29
+ prompt: string;
30
+ mediaGenerationId: string;
31
+ }
32
+ export type ImageInput = {
33
+ file: string;
34
+ } | {
35
+ url: string;
36
+ } | {
37
+ base64: string;
38
+ };
package/dist/Utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ImageExtensionTypes } from "./Types.js";
1
+ import type { ImageInput } from "./Types.js";
2
2
  /**
3
3
  * Make a request, thats all
4
4
  *
@@ -7,9 +7,10 @@ import type { ImageExtensionTypes } from "./Types.js";
7
7
  */
8
8
  export declare function request<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T>;
9
9
  /**
10
- * Returns base64 encoded image
10
+ * Fetches an image from a URL and returns base64 encoded string
11
11
  *
12
- * @param imagePath Path to image file
13
- * @param imageType Extension of image (if that matters)
12
+ * @param url URL of the image to fetch
14
13
  */
15
- export declare function imageToBase64(imagePath: string, imageType?: ImageExtensionTypes): Promise<string>;
14
+ export declare function imageFromUrl(url: string): Promise<string>;
15
+ export declare function resolveImageInput(input: ImageInput): Promise<string>;
16
+ export declare function imageToBase64(imagePath: string): Promise<string>;
package/dist/Utils.js CHANGED
@@ -20,22 +20,40 @@ export async function request(input, init) {
20
20
  return (json.result?.data?.json?.result || json);
21
21
  }
22
22
  /**
23
- * Returns base64 encoded image
23
+ * Fetches an image from a URL and returns base64 encoded string
24
24
  *
25
- * @param imagePath Path to image file
26
- * @param imageType Extension of image (if that matters)
25
+ * @param url URL of the image to fetch
27
26
  */
28
- export async function imageToBase64(imagePath, imageType) {
27
+ export async function imageFromUrl(url) {
28
+ if (!(url?.trim?.())) {
29
+ throw new Error("url is required");
30
+ }
31
+ const response = await fetch(url);
32
+ if (!response.ok) {
33
+ throw new Error(`Failed to fetch image (${response.status}): ${url}`);
34
+ }
35
+ const contentType = response.headers.get("content-type") || "";
36
+ const imageType = contentType.split("/")[1]?.split(";")[0];
37
+ if (!Object.values(ImageExtension).includes(imageType)) {
38
+ throw new Error(`'${url}': unsupported image type '${contentType}'`);
39
+ }
40
+ const buffer = Buffer.from(await response.arrayBuffer());
41
+ return `data:image/${imageType};base64,${buffer.toString("base64")}`;
42
+ }
43
+ export async function resolveImageInput(input) {
44
+ if ("file" in input)
45
+ return await imageToBase64(input.file);
46
+ if ("url" in input)
47
+ return await imageFromUrl(input.url);
48
+ return input.base64;
49
+ }
50
+ export async function imageToBase64(imagePath) {
29
51
  if (!(imagePath?.trim?.()) || !fs.existsSync(imagePath)) {
30
52
  throw new Error(`'${imagePath}': image not found`);
31
53
  }
32
- // Try to figure out image type from its extension
33
- if (!(imageType?.trim?.())) {
34
- imageType = path.extname(imagePath).slice(1);
35
- }
36
- // If extension not in valid extension list throw error
54
+ const imageType = path.extname(imagePath).slice(1);
37
55
  if (!Object.values(ImageExtension).includes(imageType)) {
38
- throw new Error(`'${imagePath}': couldn't identify image type, please specify 'imageType'`);
56
+ throw new Error(`'${imagePath}': unsupported image type '${imageType}'`);
39
57
  }
40
58
  const base64Header = `data:image/${imageType};base64,`;
41
59
  return new Promise((resolve, reject) => {
package/dist/Whisk.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Media } from "./Media.js";
2
2
  import { Project } from "./Project.js";
3
- import { PromptConfig } from "./Types.js";
3
+ import { MediaCategoryType, PromptConfig } from "./Types.js";
4
4
  export declare class Account {
5
5
  private cookie;
6
6
  private authToken?;
@@ -32,6 +32,16 @@ export declare class Whisk {
32
32
  * @param count Number of captions to generate (min: 0, max: 8)
33
33
  */
34
34
  static generateCaption(input: string, account: Account, count?: number): Promise<string[]>;
35
+ /**
36
+ * Upload a custom image to Whisk's storage
37
+ *
38
+ * @param rawBytes Base64 encoded image
39
+ * @param caption Caption describing the image
40
+ * @param category Media category (SUBJECT, SCENE, or STYLE)
41
+ * @param workflowId Project workflow id
42
+ * @param account Account{} object
43
+ */
44
+ static uploadImage(rawBytes: string, caption: string, category: MediaCategoryType, workflowId: string, account: Account): Promise<string>;
35
45
  /**
36
46
  * Tries to get media from their unique id
37
47
  *
package/dist/Whisk.js CHANGED
@@ -113,6 +113,42 @@ export class Whisk {
113
113
  });
114
114
  return captionResults.candidates.map(item => item.output);
115
115
  }
116
+ /**
117
+ * Upload a custom image to Whisk's storage
118
+ *
119
+ * @param rawBytes Base64 encoded image
120
+ * @param caption Caption describing the image
121
+ * @param category Media category (SUBJECT, SCENE, or STYLE)
122
+ * @param workflowId Project workflow id
123
+ * @param account Account{} object
124
+ */
125
+ static async uploadImage(rawBytes, caption, category, workflowId, account) {
126
+ if (!(rawBytes?.trim?.())) {
127
+ throw new Error("image data is required");
128
+ }
129
+ if (!(caption?.trim?.())) {
130
+ throw new Error("caption is required");
131
+ }
132
+ if (!(account instanceof Account)) {
133
+ throw new Error("invalid or missing account");
134
+ }
135
+ const uploadResult = await request("https://labs.google/fx/api/trpc/backbone.uploadImage", {
136
+ headers: { cookie: account.getCookie() },
137
+ body: JSON.stringify({
138
+ "json": {
139
+ "clientContext": {
140
+ "workflowId": workflowId
141
+ },
142
+ "uploadMediaInput": {
143
+ "mediaCategory": category,
144
+ "rawBytes": rawBytes,
145
+ "caption": caption
146
+ }
147
+ }
148
+ })
149
+ });
150
+ return uploadResult.uploadMediaGenerationId;
151
+ }
116
152
  /**
117
153
  * Tries to get media from their unique id
118
154
  *
package/dist/index.d.ts CHANGED
@@ -3,3 +3,5 @@ export { Whisk } from "./Whisk.js";
3
3
  export { Media } from "./Media.js";
4
4
  export { Project } from "./Project.js";
5
5
  export * from "./Types.js";
6
+ export * from "./Constants.js";
7
+ export { imageToBase64, imageFromUrl } from "./Utils.js";
package/dist/index.js CHANGED
@@ -3,3 +3,5 @@ export { Whisk } from "./Whisk.js";
3
3
  export { Media } from "./Media.js";
4
4
  export { Project } from "./Project.js";
5
5
  export * from "./Types.js";
6
+ export * from "./Constants.js";
7
+ export { imageToBase64, imageFromUrl } from "./Utils.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohitaryal/whisk-api",
3
- "version": "3.2.0",
3
+ "version": "4.0.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",