@rohitaryal/whisk-api 3.0.0 → 3.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/dist/Cli.d.ts +2 -0
- package/dist/Cli.js +270 -0
- package/dist/Constants.d.ts +37 -0
- package/dist/Constants.js +37 -0
- package/dist/Media.d.ts +47 -0
- package/dist/Media.js +191 -0
- package/dist/Project.d.ts +13 -0
- package/dist/Project.js +79 -0
- package/dist/Types.d.ts +26 -0
- package/dist/Types.js +1 -0
- package/dist/Utils.d.ts +15 -0
- package/dist/Utils.js +48 -0
- package/dist/Whisk.d.ts +46 -0
- package/dist/Whisk.js +165 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/package.json +6 -6
package/dist/Cli.d.ts
ADDED
package/dist/Cli.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import { hideBin } from 'yargs/helpers';
|
|
4
|
+
import { Whisk } from "./Whisk.js";
|
|
5
|
+
import { imageToBase64 } from "./Utils.js";
|
|
6
|
+
import { ImageAspectRatio, ImageGenerationModel, VideoGenerationModel } from "./Constants.js";
|
|
7
|
+
const y = yargs(hideBin(process.argv));
|
|
8
|
+
await y
|
|
9
|
+
.scriptName("whisk")
|
|
10
|
+
.usage('$0 <cmd> [args]')
|
|
11
|
+
.command("generate", "Generate new images using a temporary project", (yargs) => {
|
|
12
|
+
return yargs
|
|
13
|
+
.option("prompt", {
|
|
14
|
+
alias: "p",
|
|
15
|
+
describe: "Description of the image",
|
|
16
|
+
type: "string",
|
|
17
|
+
demandOption: true,
|
|
18
|
+
})
|
|
19
|
+
.option("model", {
|
|
20
|
+
alias: "m",
|
|
21
|
+
describe: "Image generation model",
|
|
22
|
+
type: "string",
|
|
23
|
+
default: "IMAGEN_3_5",
|
|
24
|
+
choices: Object.keys(ImageGenerationModel)
|
|
25
|
+
})
|
|
26
|
+
.option("aspect", {
|
|
27
|
+
alias: "a",
|
|
28
|
+
describe: "Aspect ratio",
|
|
29
|
+
type: "string",
|
|
30
|
+
default: "LANDSCAPE",
|
|
31
|
+
choices: ["SQUARE", "PORTRAIT", "LANDSCAPE"]
|
|
32
|
+
})
|
|
33
|
+
.option("seed", {
|
|
34
|
+
alias: "s",
|
|
35
|
+
describe: "Seed value (0 for random)",
|
|
36
|
+
type: "number",
|
|
37
|
+
default: 0,
|
|
38
|
+
})
|
|
39
|
+
.option("dir", {
|
|
40
|
+
alias: "d",
|
|
41
|
+
describe: "Output directory",
|
|
42
|
+
type: "string",
|
|
43
|
+
default: "./output",
|
|
44
|
+
})
|
|
45
|
+
.option("cookie", {
|
|
46
|
+
alias: "c",
|
|
47
|
+
describe: "Google account cookie",
|
|
48
|
+
type: "string",
|
|
49
|
+
demandOption: true,
|
|
50
|
+
});
|
|
51
|
+
}, async (argv) => {
|
|
52
|
+
const whisk = new Whisk(argv.cookie);
|
|
53
|
+
await whisk.account.refresh();
|
|
54
|
+
console.log(whisk.account.toString());
|
|
55
|
+
console.log("[*] Initializing session...");
|
|
56
|
+
const project = await whisk.newProject(`CLI-Gen-${Date.now()}`);
|
|
57
|
+
console.log(`[*] Created temporary workflow: ${project.projectId}`);
|
|
58
|
+
try {
|
|
59
|
+
console.log("[*] Generating image...");
|
|
60
|
+
const aspectKey = argv.aspect;
|
|
61
|
+
const aspectVal = ImageAspectRatio[aspectKey];
|
|
62
|
+
const media = await project.generateImage({
|
|
63
|
+
prompt: argv.prompt,
|
|
64
|
+
model: ImageGenerationModel[argv.model],
|
|
65
|
+
aspectRatio: aspectVal,
|
|
66
|
+
seed: argv.seed
|
|
67
|
+
});
|
|
68
|
+
const savedPath = media.save(argv.dir);
|
|
69
|
+
console.log(`[+] Image generated successfully!`);
|
|
70
|
+
console.log(`[+] Saved to: ${savedPath}`);
|
|
71
|
+
console.log(`[+] ID: ${media.mediaGenerationId}`);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error("[!] Generation failed:", error);
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
.command("animate <mediaId>", "Animate an existing landscape image into a video", (yargs) => {
|
|
78
|
+
return yargs
|
|
79
|
+
.positional("mediaId", {
|
|
80
|
+
describe: "The ID of the image to animate",
|
|
81
|
+
type: "string",
|
|
82
|
+
demandOption: true
|
|
83
|
+
})
|
|
84
|
+
.option("script", {
|
|
85
|
+
alias: "s",
|
|
86
|
+
describe: "Prompt/Script for the video animation",
|
|
87
|
+
type: "string",
|
|
88
|
+
demandOption: true
|
|
89
|
+
})
|
|
90
|
+
.option("model", {
|
|
91
|
+
alias: "m",
|
|
92
|
+
describe: "Video generation model",
|
|
93
|
+
type: "string",
|
|
94
|
+
default: "VEO_FAST_3_1",
|
|
95
|
+
choices: Object.keys(VideoGenerationModel)
|
|
96
|
+
})
|
|
97
|
+
.option("dir", {
|
|
98
|
+
alias: "d",
|
|
99
|
+
describe: "Output directory",
|
|
100
|
+
type: "string",
|
|
101
|
+
default: "./output",
|
|
102
|
+
})
|
|
103
|
+
.option("cookie", {
|
|
104
|
+
alias: "c",
|
|
105
|
+
describe: "Google account cookie",
|
|
106
|
+
type: "string",
|
|
107
|
+
demandOption: true,
|
|
108
|
+
});
|
|
109
|
+
}, async (argv) => {
|
|
110
|
+
const whisk = new Whisk(argv.cookie);
|
|
111
|
+
await whisk.account.refresh();
|
|
112
|
+
console.log(whisk.account.toString());
|
|
113
|
+
console.log("[*] Fetching original media...");
|
|
114
|
+
try {
|
|
115
|
+
const originalMedia = await Whisk.getMedia(argv.mediaId, whisk.account);
|
|
116
|
+
console.log("[*] Requesting animation (this takes time)...");
|
|
117
|
+
const videoMedia = await originalMedia.animate(argv.script, VideoGenerationModel[argv.model]);
|
|
118
|
+
const savedPath = videoMedia.save(argv.dir);
|
|
119
|
+
console.log(`[+] Video generated successfully!`);
|
|
120
|
+
console.log(`[+] Saved to: ${savedPath}`);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error("[!] Animation failed:", error);
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
.command("refine <mediaId>", "Edit/Refine an existing image", (yargs) => {
|
|
127
|
+
return yargs
|
|
128
|
+
.positional("mediaId", {
|
|
129
|
+
describe: "The ID of the image to refine",
|
|
130
|
+
type: "string",
|
|
131
|
+
demandOption: true
|
|
132
|
+
})
|
|
133
|
+
.option("prompt", {
|
|
134
|
+
alias: "p",
|
|
135
|
+
describe: "Instruction for editing (e.g., 'Make it snowy')",
|
|
136
|
+
type: "string",
|
|
137
|
+
demandOption: true
|
|
138
|
+
})
|
|
139
|
+
.option("dir", {
|
|
140
|
+
alias: "d",
|
|
141
|
+
describe: "Output directory",
|
|
142
|
+
type: "string",
|
|
143
|
+
default: "./output",
|
|
144
|
+
})
|
|
145
|
+
.option("cookie", {
|
|
146
|
+
alias: "c",
|
|
147
|
+
describe: "Google account cookie",
|
|
148
|
+
type: "string",
|
|
149
|
+
demandOption: true,
|
|
150
|
+
});
|
|
151
|
+
}, async (argv) => {
|
|
152
|
+
const whisk = new Whisk(argv.cookie);
|
|
153
|
+
await whisk.account.refresh();
|
|
154
|
+
console.log(whisk.account.toString());
|
|
155
|
+
console.log("[*] Fetching original media...");
|
|
156
|
+
try {
|
|
157
|
+
const originalMedia = await Whisk.getMedia(argv.mediaId, whisk.account);
|
|
158
|
+
console.log("[*] Refining image...");
|
|
159
|
+
const refinedMedia = await originalMedia.refine(argv.prompt);
|
|
160
|
+
const savedPath = refinedMedia.save(argv.dir);
|
|
161
|
+
console.log(`[+] Image refined successfully!`);
|
|
162
|
+
console.log(`[+] Saved to: ${savedPath}`);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error("[!] Refinement failed:", error);
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.command("caption", "Generate captions for a local image file", (yargs) => {
|
|
169
|
+
return yargs
|
|
170
|
+
.option("file", {
|
|
171
|
+
alias: "f",
|
|
172
|
+
describe: "Path to local image file",
|
|
173
|
+
type: "string",
|
|
174
|
+
demandOption: true
|
|
175
|
+
})
|
|
176
|
+
.option("count", {
|
|
177
|
+
alias: "n",
|
|
178
|
+
describe: "Number of captions",
|
|
179
|
+
type: "number",
|
|
180
|
+
default: 1
|
|
181
|
+
})
|
|
182
|
+
.option("cookie", {
|
|
183
|
+
alias: "c",
|
|
184
|
+
describe: "Google account cookie",
|
|
185
|
+
type: "string",
|
|
186
|
+
demandOption: true,
|
|
187
|
+
});
|
|
188
|
+
}, async (argv) => {
|
|
189
|
+
const whisk = new Whisk(argv.cookie);
|
|
190
|
+
await whisk.account.refresh();
|
|
191
|
+
console.log(whisk.account.toString());
|
|
192
|
+
console.log("[*] Reading file and generating captions...");
|
|
193
|
+
try {
|
|
194
|
+
const base64 = await imageToBase64(argv.file);
|
|
195
|
+
// Split at the comma to get the raw base64 string
|
|
196
|
+
const rawBase64 = base64.split(",")[1];
|
|
197
|
+
const captions = await Whisk.generateCaption(rawBase64, whisk.account, argv.count);
|
|
198
|
+
console.log("\n--- Captions ---");
|
|
199
|
+
captions.forEach((cap, i) => {
|
|
200
|
+
console.log(`[${i + 1}] ${cap}`);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.error("[!] Captioning failed:", error);
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
.command("fetch <mediaId>", "Download an existing generated media by ID", (yargs) => {
|
|
208
|
+
return yargs
|
|
209
|
+
.positional("mediaId", {
|
|
210
|
+
describe: "Unique ID of generated media",
|
|
211
|
+
type: "string",
|
|
212
|
+
demandOption: true,
|
|
213
|
+
})
|
|
214
|
+
.option("dir", {
|
|
215
|
+
alias: "d",
|
|
216
|
+
describe: "Output directory",
|
|
217
|
+
type: "string",
|
|
218
|
+
default: "./output",
|
|
219
|
+
})
|
|
220
|
+
.option("cookie", {
|
|
221
|
+
alias: "c",
|
|
222
|
+
describe: "Google account cookie",
|
|
223
|
+
type: "string",
|
|
224
|
+
demandOption: true,
|
|
225
|
+
});
|
|
226
|
+
}, async (argv) => {
|
|
227
|
+
const whisk = new Whisk(argv.cookie);
|
|
228
|
+
await whisk.account.refresh();
|
|
229
|
+
console.log(whisk.account.toString());
|
|
230
|
+
try {
|
|
231
|
+
console.log(`[*] Fetching media info for ${argv.mediaId}...`);
|
|
232
|
+
const media = await Whisk.getMedia(argv.mediaId, whisk.account);
|
|
233
|
+
const savedPath = media.save(argv.dir);
|
|
234
|
+
console.log(`[+] Downloaded successfully to: ${savedPath}`);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
console.error("[!] Fetch failed:", error);
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
.command("delete <mediaId>", "Delete a generated media from the cloud", (yargs) => {
|
|
241
|
+
return yargs
|
|
242
|
+
.positional("mediaId", {
|
|
243
|
+
describe: "Unique ID of generated media to delete",
|
|
244
|
+
type: "string",
|
|
245
|
+
demandOption: true,
|
|
246
|
+
})
|
|
247
|
+
.option("cookie", {
|
|
248
|
+
alias: "c",
|
|
249
|
+
describe: "Google account cookie",
|
|
250
|
+
type: "string",
|
|
251
|
+
demandOption: true,
|
|
252
|
+
});
|
|
253
|
+
}, async (argv) => {
|
|
254
|
+
const whisk = new Whisk(argv.cookie);
|
|
255
|
+
await whisk.account.refresh();
|
|
256
|
+
console.log(whisk.account.toString());
|
|
257
|
+
try {
|
|
258
|
+
console.log(`[*] Deleting media ${argv.mediaId}...`);
|
|
259
|
+
await Whisk.deleteMedia(argv.mediaId, whisk.account);
|
|
260
|
+
console.log(`[+] Media deleted.`);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
console.error("[!] Delete failed:", error);
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
.demandCommand(1, "You need to provide a command")
|
|
267
|
+
.strict()
|
|
268
|
+
.help()
|
|
269
|
+
.alias("help", "h")
|
|
270
|
+
.parse();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const ImageExtension: Readonly<{
|
|
2
|
+
readonly JPG: "jpg";
|
|
3
|
+
readonly JPEG: "jpeg";
|
|
4
|
+
readonly PNG: "png";
|
|
5
|
+
readonly GIF: "gif";
|
|
6
|
+
readonly WEBP: "webp";
|
|
7
|
+
readonly AVIF: "avif";
|
|
8
|
+
readonly HEIC: "heic";
|
|
9
|
+
readonly HEIF: "heif";
|
|
10
|
+
readonly BMP: "bmp";
|
|
11
|
+
readonly TIF: "tif";
|
|
12
|
+
readonly TIFF: "tiff";
|
|
13
|
+
readonly SVG: "svg";
|
|
14
|
+
readonly ICO: "ico";
|
|
15
|
+
}>;
|
|
16
|
+
export declare const ImageAspectRatio: Readonly<{
|
|
17
|
+
readonly SQUARE: "IMAGE_ASPECT_RATIO_SQUARE";
|
|
18
|
+
readonly PORTRAIT: "IMAGE_ASPECT_RATIO_PORTRAIT";
|
|
19
|
+
readonly LANDSCAPE: "IMAGE_ASPECT_RATIO_LANDSCAPE";
|
|
20
|
+
readonly UNSPECIFIED: "IMAGE_ASPECT_RATIO_UNSPECIFIED";
|
|
21
|
+
}>;
|
|
22
|
+
export declare const VideoAspectRatio: Readonly<{
|
|
23
|
+
readonly SQUARE: "VIDEO_ASPECT_RATIO_SQUARE";
|
|
24
|
+
readonly PORTRAIT: "VIDEO_ASPECT_RATIO_PORTRAIT";
|
|
25
|
+
readonly LANDSCAPE: "VIDEO_ASPECT_RATIO_LANDSCAPE";
|
|
26
|
+
readonly UNSPECIFIED: "VIDEO_ASPECT_RATIO_UNSPECIFIED";
|
|
27
|
+
}>;
|
|
28
|
+
export declare const ImageGenerationModel: Readonly<{
|
|
29
|
+
readonly IMAGEN_3_5: "IMAGEN_3_5";
|
|
30
|
+
}>;
|
|
31
|
+
export declare const ImageRefinementModel: Readonly<{
|
|
32
|
+
GEM_PIX: "GEM_PIX";
|
|
33
|
+
}>;
|
|
34
|
+
export declare const VideoGenerationModel: Readonly<{
|
|
35
|
+
readonly VEO_3_1: "VEO_3_1_I2V_12STEP";
|
|
36
|
+
readonly VEO_FAST_3_1: "veo_3_1_i2v_s_fast";
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const ImageExtension = Object.freeze({
|
|
2
|
+
JPG: "jpg",
|
|
3
|
+
JPEG: "jpeg",
|
|
4
|
+
PNG: "png",
|
|
5
|
+
GIF: "gif",
|
|
6
|
+
WEBP: "webp",
|
|
7
|
+
AVIF: "avif",
|
|
8
|
+
HEIC: "heic",
|
|
9
|
+
HEIF: "heif",
|
|
10
|
+
BMP: "bmp",
|
|
11
|
+
TIF: "tif",
|
|
12
|
+
TIFF: "tiff",
|
|
13
|
+
SVG: "svg",
|
|
14
|
+
ICO: "ico"
|
|
15
|
+
});
|
|
16
|
+
export const ImageAspectRatio = Object.freeze({
|
|
17
|
+
SQUARE: "IMAGE_ASPECT_RATIO_SQUARE",
|
|
18
|
+
PORTRAIT: "IMAGE_ASPECT_RATIO_PORTRAIT",
|
|
19
|
+
LANDSCAPE: "IMAGE_ASPECT_RATIO_LANDSCAPE",
|
|
20
|
+
UNSPECIFIED: "IMAGE_ASPECT_RATIO_UNSPECIFIED",
|
|
21
|
+
});
|
|
22
|
+
export const VideoAspectRatio = Object.freeze({
|
|
23
|
+
SQUARE: "VIDEO_ASPECT_RATIO_SQUARE",
|
|
24
|
+
PORTRAIT: "VIDEO_ASPECT_RATIO_PORTRAIT",
|
|
25
|
+
LANDSCAPE: "VIDEO_ASPECT_RATIO_LANDSCAPE",
|
|
26
|
+
UNSPECIFIED: "VIDEO_ASPECT_RATIO_UNSPECIFIED",
|
|
27
|
+
});
|
|
28
|
+
export const ImageGenerationModel = Object.freeze({
|
|
29
|
+
IMAGEN_3_5: "IMAGEN_3_5",
|
|
30
|
+
});
|
|
31
|
+
export const ImageRefinementModel = Object.freeze({
|
|
32
|
+
GEM_PIX: "GEM_PIX",
|
|
33
|
+
});
|
|
34
|
+
export const VideoGenerationModel = Object.freeze({
|
|
35
|
+
VEO_3_1: "VEO_3_1_I2V_12STEP",
|
|
36
|
+
VEO_FAST_3_1: "veo_3_1_i2v_s_fast",
|
|
37
|
+
});
|
package/dist/Media.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Account } from "./Whisk.js";
|
|
2
|
+
import type { VideoAspectRatioType, ImageAspectRatioType, ImageGenerationModelType, MediaConfig, VideoGenerationModelType, ImageRefinementModelType } from "./Types.js";
|
|
3
|
+
export declare class Media {
|
|
4
|
+
readonly seed: number;
|
|
5
|
+
readonly prompt: string;
|
|
6
|
+
readonly refined?: boolean;
|
|
7
|
+
readonly workflowId: string;
|
|
8
|
+
readonly encodedMedia: string;
|
|
9
|
+
readonly mediaGenerationId: string;
|
|
10
|
+
readonly mediaType: "VIDEO" | "IMAGE";
|
|
11
|
+
readonly aspectRatio: ImageAspectRatioType | VideoAspectRatioType;
|
|
12
|
+
readonly model: ImageGenerationModelType | VideoGenerationModelType | ImageRefinementModelType;
|
|
13
|
+
readonly account: Account;
|
|
14
|
+
constructor(mediaConfig: MediaConfig);
|
|
15
|
+
/**
|
|
16
|
+
* Deletes the generated media
|
|
17
|
+
*/
|
|
18
|
+
deleteMedia(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Image to Text but doesn't support videos
|
|
21
|
+
*
|
|
22
|
+
* @param count Number of captions to generate (min: 1, max: 8)
|
|
23
|
+
*/
|
|
24
|
+
caption(count?: number): Promise<string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Refine/Edit an image using nano banana
|
|
27
|
+
*
|
|
28
|
+
* @param edit Refinement prompt
|
|
29
|
+
* @returns Refined image
|
|
30
|
+
*/
|
|
31
|
+
refine(edit: string): Promise<Media>;
|
|
32
|
+
/**
|
|
33
|
+
* Initiates video animation request
|
|
34
|
+
* Note: Only landscape images can be animated
|
|
35
|
+
*
|
|
36
|
+
* @param videoScript Video script to be followed
|
|
37
|
+
* @param model Video generation model to be used
|
|
38
|
+
*/
|
|
39
|
+
animate(videoScript: string, model: VideoGenerationModelType): Promise<Media>;
|
|
40
|
+
/**
|
|
41
|
+
* Saves the media to the local disk
|
|
42
|
+
*
|
|
43
|
+
* @param directory Directory path to save the media (default: current directory)
|
|
44
|
+
* @returns The absolute path of the saved file
|
|
45
|
+
*/
|
|
46
|
+
save(directory?: string): string;
|
|
47
|
+
}
|
package/dist/Media.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { Whisk } from "./Whisk.js";
|
|
3
|
+
import { request } from "./Utils.js";
|
|
4
|
+
import { VideoGenerationModel } from "./Constants.js";
|
|
5
|
+
import path from "path";
|
|
6
|
+
export class Media {
|
|
7
|
+
seed;
|
|
8
|
+
prompt;
|
|
9
|
+
refined;
|
|
10
|
+
workflowId;
|
|
11
|
+
encodedMedia;
|
|
12
|
+
mediaGenerationId;
|
|
13
|
+
mediaType;
|
|
14
|
+
aspectRatio;
|
|
15
|
+
model;
|
|
16
|
+
account;
|
|
17
|
+
constructor(mediaConfig) {
|
|
18
|
+
if (!(mediaConfig.encodedMedia)?.trim?.()) {
|
|
19
|
+
throw new Error("invalid or empty media");
|
|
20
|
+
}
|
|
21
|
+
this.seed = mediaConfig.seed;
|
|
22
|
+
this.prompt = mediaConfig.prompt;
|
|
23
|
+
this.workflowId = mediaConfig.workflowId;
|
|
24
|
+
this.encodedMedia = mediaConfig.encodedMedia;
|
|
25
|
+
this.mediaGenerationId = mediaConfig.mediaGenerationId;
|
|
26
|
+
this.aspectRatio = mediaConfig.aspectRatio;
|
|
27
|
+
this.mediaType = mediaConfig.mediaType;
|
|
28
|
+
this.model = mediaConfig.model;
|
|
29
|
+
this.refined = mediaConfig.refined;
|
|
30
|
+
this.account = mediaConfig.account;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Deletes the generated media
|
|
34
|
+
*/
|
|
35
|
+
async deleteMedia() {
|
|
36
|
+
await Whisk.deleteMedia(this.mediaGenerationId, this.account);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Image to Text but doesn't support videos
|
|
40
|
+
*
|
|
41
|
+
* @param count Number of captions to generate (min: 1, max: 8)
|
|
42
|
+
*/
|
|
43
|
+
async caption(count = 1) {
|
|
44
|
+
if (this.mediaType === "VIDEO") {
|
|
45
|
+
throw new Error("videos can't be captioned");
|
|
46
|
+
}
|
|
47
|
+
if (count <= 0 || count > 8) {
|
|
48
|
+
throw new Error("count must be in between 0 and 9 (0 < count < 9)");
|
|
49
|
+
}
|
|
50
|
+
return await Whisk.generateCaption(this.encodedMedia, this.account, count);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Refine/Edit an image using nano banana
|
|
54
|
+
*
|
|
55
|
+
* @param edit Refinement prompt
|
|
56
|
+
* @returns Refined image
|
|
57
|
+
*/
|
|
58
|
+
async refine(edit) {
|
|
59
|
+
if (this.mediaType === "VIDEO") {
|
|
60
|
+
throw new Error("can't refine a video");
|
|
61
|
+
}
|
|
62
|
+
const refinementResult = await request("https://labs.google/fx/api/trpc/backbone.editImage", {
|
|
63
|
+
headers: { cookie: this.account.getCookie() },
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
"json": {
|
|
66
|
+
"clientContext": {
|
|
67
|
+
"workflowId": this.workflowId
|
|
68
|
+
},
|
|
69
|
+
"imageModelSettings": {
|
|
70
|
+
"aspectRatio": this.aspectRatio,
|
|
71
|
+
"imageModel": "GEM_PIX",
|
|
72
|
+
},
|
|
73
|
+
"editInput": {
|
|
74
|
+
"caption": this.prompt,
|
|
75
|
+
"userInstruction": edit,
|
|
76
|
+
"seed": null,
|
|
77
|
+
"safetyMode": null,
|
|
78
|
+
"originalMediaGenerationId": this.mediaGenerationId,
|
|
79
|
+
"mediaInput": {
|
|
80
|
+
"mediaCategory": "MEDIA_CATEGORY_BOARD",
|
|
81
|
+
"rawBytes": this.encodedMedia
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"meta": {
|
|
86
|
+
"values": {
|
|
87
|
+
"editInput.seed": ["undefined"],
|
|
88
|
+
"editInput.safetyMode": ["undefined"]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
const img = refinementResult.imagePanels[0].generatedImages[0];
|
|
94
|
+
return new Media({
|
|
95
|
+
seed: this.seed,
|
|
96
|
+
prompt: img.prompt,
|
|
97
|
+
workflowId: img.workflowId,
|
|
98
|
+
encodedMedia: img.encodedImage,
|
|
99
|
+
mediaGenerationId: this.mediaGenerationId,
|
|
100
|
+
refined: true,
|
|
101
|
+
aspectRatio: img.aspectRatio,
|
|
102
|
+
mediaType: "IMAGE",
|
|
103
|
+
model: img.imageModel,
|
|
104
|
+
account: this.account,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Initiates video animation request
|
|
109
|
+
* Note: Only landscape images can be animated
|
|
110
|
+
*
|
|
111
|
+
* @param videoScript Video script to be followed
|
|
112
|
+
* @param model Video generation model to be used
|
|
113
|
+
*/
|
|
114
|
+
async animate(videoScript, model) {
|
|
115
|
+
if (this.mediaType === "VIDEO") {
|
|
116
|
+
throw new Error("can't animate a video");
|
|
117
|
+
}
|
|
118
|
+
if (this.aspectRatio !== "IMAGE_ASPECT_RATIO_LANDSCAPE") {
|
|
119
|
+
throw new Error("only landscape images can be animated");
|
|
120
|
+
}
|
|
121
|
+
if (!Object.values(VideoGenerationModel).includes(model)) {
|
|
122
|
+
throw new Error(`'${model}': invalid video generation model provided`);
|
|
123
|
+
}
|
|
124
|
+
const videoStatusResults = await request("https://aisandbox-pa.googleapis.com/v1/whisk:generateVideo", {
|
|
125
|
+
headers: {
|
|
126
|
+
"Authorization": `Bearer ${await this.account.getToken()}`
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
"promptImageInput": {
|
|
130
|
+
"prompt": this.prompt,
|
|
131
|
+
"rawBytes": this.encodedMedia,
|
|
132
|
+
},
|
|
133
|
+
"modelNameType": model,
|
|
134
|
+
"modelKey": "",
|
|
135
|
+
"userInstructions": videoScript,
|
|
136
|
+
"loopVideo": false,
|
|
137
|
+
"clientContext": { "workflowId": this.workflowId },
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
let i = 0;
|
|
141
|
+
const id = videoStatusResults.operation.operation.name;
|
|
142
|
+
while (true) {
|
|
143
|
+
i++;
|
|
144
|
+
const videoResults = await request("https://aisandbox-pa.googleapis.com/v1:runVideoFxSingleClipsStatusCheck", {
|
|
145
|
+
headers: {
|
|
146
|
+
"Authorization": `Bearer ${await this.account.getToken()}`
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({ "operations": [{ "operation": { "name": id } }] })
|
|
149
|
+
});
|
|
150
|
+
// Await for 2 second before each request
|
|
151
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
152
|
+
if (videoResults.status === "MEDIA_GENERATION_STATUS_SUCCESSFUL") {
|
|
153
|
+
const video = videoResults.operation.metadata.video;
|
|
154
|
+
return new Media({
|
|
155
|
+
seed: video.seed,
|
|
156
|
+
prompt: video.prompt,
|
|
157
|
+
workflowId: this.workflowId,
|
|
158
|
+
encodedMedia: videoResults.rawBytes,
|
|
159
|
+
mediaGenerationId: videoResults.mediaGenerationId,
|
|
160
|
+
aspectRatio: video.aspectRatio,
|
|
161
|
+
mediaType: "VIDEO",
|
|
162
|
+
model: video.model,
|
|
163
|
+
account: this.account,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (i >= 20) {
|
|
167
|
+
throw new Error("failed to generate video: " + videoResults);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Saves the media to the local disk
|
|
173
|
+
*
|
|
174
|
+
* @param directory Directory path to save the media (default: current directory)
|
|
175
|
+
* @returns The absolute path of the saved file
|
|
176
|
+
*/
|
|
177
|
+
save(directory = ".") {
|
|
178
|
+
if (!fs.existsSync(directory)) {
|
|
179
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
let extension = this.mediaType == "VIDEO" ? "mp4" : "png";
|
|
182
|
+
const base64Data = this.encodedMedia.replace(/^data:\w+\/\w+;base64,/, "");
|
|
183
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
184
|
+
const timestamp = Date.now();
|
|
185
|
+
const uuidShort = this.mediaGenerationId.slice(0, 4);
|
|
186
|
+
const fileName = `img_${timestamp}_${uuidShort}.${extension}`;
|
|
187
|
+
const filePath = path.resolve(directory, fileName);
|
|
188
|
+
fs.writeFileSync(filePath, buffer);
|
|
189
|
+
return filePath;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Media } from "./Media.js";
|
|
2
|
+
import type { PromptConfig } from "./Types.js";
|
|
3
|
+
import { Account } from "./Whisk.js";
|
|
4
|
+
export declare class Project {
|
|
5
|
+
readonly account: Account;
|
|
6
|
+
readonly projectId: string;
|
|
7
|
+
constructor(projectId: string, account: Account);
|
|
8
|
+
generateImage(input: string | PromptConfig): Promise<Media>;
|
|
9
|
+
/**
|
|
10
|
+
* Deletes the project, clearance of your slop from the existance
|
|
11
|
+
*/
|
|
12
|
+
delete(): Promise<void>;
|
|
13
|
+
}
|
package/dist/Project.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ImageGenerationModel } from "./Constants.js";
|
|
2
|
+
import { Media } from "./Media.js";
|
|
3
|
+
import { request } from "./Utils.js";
|
|
4
|
+
import { Account } from "./Whisk.js";
|
|
5
|
+
export class Project {
|
|
6
|
+
account;
|
|
7
|
+
projectId;
|
|
8
|
+
constructor(projectId, account) {
|
|
9
|
+
if (typeof projectId !== "string" || !projectId.trim()) {
|
|
10
|
+
throw new Error("project id is either invalid or missing");
|
|
11
|
+
}
|
|
12
|
+
if (!(account instanceof Account)) {
|
|
13
|
+
throw new Error("account is invalid or missing");
|
|
14
|
+
}
|
|
15
|
+
this.projectId = projectId;
|
|
16
|
+
this.account = account;
|
|
17
|
+
}
|
|
18
|
+
async generateImage(input) {
|
|
19
|
+
if (typeof input === "string") {
|
|
20
|
+
input = {
|
|
21
|
+
seed: 0,
|
|
22
|
+
prompt: input,
|
|
23
|
+
model: "IMAGEN_3_5",
|
|
24
|
+
aspectRatio: "IMAGE_ASPECT_RATIO_LANDSCAPE"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (!input.seed)
|
|
28
|
+
input.seed = 0;
|
|
29
|
+
if (!input.model)
|
|
30
|
+
input.model = "IMAGEN_3_5";
|
|
31
|
+
if (!input.aspectRatio)
|
|
32
|
+
input.aspectRatio = "IMAGE_ASPECT_RATIO_LANDSCAPE";
|
|
33
|
+
if (!input.prompt?.trim?.())
|
|
34
|
+
throw new Error("prompt is required");
|
|
35
|
+
if (!Object.values(ImageGenerationModel).includes(input.model)) {
|
|
36
|
+
throw new Error(`'${input.model}': invalid image generation model provided`);
|
|
37
|
+
}
|
|
38
|
+
const generationResponse = await request("https://aisandbox-pa.googleapis.com/v1/whisk:generateImage", {
|
|
39
|
+
headers: { authorization: `Bearer ${await this.account.getToken()}` },
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
"clientContext": {
|
|
42
|
+
"workflowId": this.projectId
|
|
43
|
+
},
|
|
44
|
+
"imageModelSettings": {
|
|
45
|
+
"imageModel": input.model,
|
|
46
|
+
"aspectRatio": input.aspectRatio
|
|
47
|
+
},
|
|
48
|
+
"seed": input.seed,
|
|
49
|
+
"prompt": input.prompt,
|
|
50
|
+
"mediaCategory": "MEDIA_CATEGORY_BOARD"
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
const img = generationResponse.imagePanels[0].generatedImages[0];
|
|
54
|
+
return new Media({
|
|
55
|
+
seed: img.seed,
|
|
56
|
+
prompt: img.prompt,
|
|
57
|
+
workflowId: img.workflowId ?? generationResponse.workflowId,
|
|
58
|
+
encodedMedia: img.encodedImage,
|
|
59
|
+
mediaGenerationId: img.mediaGenerationId,
|
|
60
|
+
aspectRatio: img.aspectRatio,
|
|
61
|
+
mediaType: "IMAGE",
|
|
62
|
+
model: img.imageModel,
|
|
63
|
+
account: this.account
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Deletes the project, clearance of your slop from the existance
|
|
68
|
+
*/
|
|
69
|
+
async delete() {
|
|
70
|
+
await request("https://labs.google/fx/api/trpc/media.deleteMedia", {
|
|
71
|
+
headers: { cookie: this.account.getCookie() },
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
"json": {
|
|
74
|
+
"parent": "userProject/", "names": [this.projectId]
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/Types.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Account } from "./Whisk.js";
|
|
2
|
+
import { ImageAspectRatio, VideoAspectRatio, ImageExtension, ImageGenerationModel, VideoGenerationModel, ImageRefinementModel } from "./Constants.js";
|
|
3
|
+
export interface MediaConfig {
|
|
4
|
+
seed: number;
|
|
5
|
+
prompt: string;
|
|
6
|
+
refined?: boolean;
|
|
7
|
+
workflowId: string;
|
|
8
|
+
encodedMedia: string;
|
|
9
|
+
mediaGenerationId: string;
|
|
10
|
+
mediaType: "VIDEO" | "IMAGE";
|
|
11
|
+
aspectRatio: ImageAspectRatioType | VideoAspectRatioType;
|
|
12
|
+
model: ImageGenerationModelType | VideoGenerationModelType;
|
|
13
|
+
account: Account;
|
|
14
|
+
}
|
|
15
|
+
export interface PromptConfig {
|
|
16
|
+
seed?: number;
|
|
17
|
+
prompt: string;
|
|
18
|
+
aspectRatio?: ImageAspectRatioType | VideoAspectRatioType;
|
|
19
|
+
model?: ImageGenerationModelType | VideoGenerationModelType;
|
|
20
|
+
}
|
|
21
|
+
export type ImageAspectRatioType = typeof ImageAspectRatio[keyof typeof ImageAspectRatio];
|
|
22
|
+
export type VideoAspectRatioType = typeof VideoAspectRatio[keyof typeof VideoAspectRatio];
|
|
23
|
+
export type ImageExtensionTypes = typeof ImageExtension[keyof typeof ImageExtension];
|
|
24
|
+
export type ImageGenerationModelType = typeof ImageGenerationModel[keyof typeof ImageGenerationModel];
|
|
25
|
+
export type VideoGenerationModelType = typeof VideoGenerationModel[keyof typeof VideoGenerationModel];
|
|
26
|
+
export type ImageRefinementModelType = typeof ImageRefinementModel[keyof typeof ImageRefinementModel];
|
package/dist/Types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Utils.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ImageExtensionTypes } from "./Types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Make a request, thats all
|
|
4
|
+
*
|
|
5
|
+
* @param input URL or Request object
|
|
6
|
+
* @param init Settings for request() method
|
|
7
|
+
*/
|
|
8
|
+
export declare function request<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns base64 encoded image
|
|
11
|
+
*
|
|
12
|
+
* @param imagePath Path to image file
|
|
13
|
+
* @param imageType Extension of image (if that matters)
|
|
14
|
+
*/
|
|
15
|
+
export declare function imageToBase64(imagePath: string, imageType?: ImageExtensionTypes): Promise<string>;
|
package/dist/Utils.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { ImageExtension } from "./Constants.js";
|
|
4
|
+
/**
|
|
5
|
+
* Make a request, thats all
|
|
6
|
+
*
|
|
7
|
+
* @param input URL or Request object
|
|
8
|
+
* @param init Settings for request() method
|
|
9
|
+
*/
|
|
10
|
+
export async function request(input, init) {
|
|
11
|
+
if (init) {
|
|
12
|
+
init.method = init.method ?? (init.body ? "POST" : "GET");
|
|
13
|
+
}
|
|
14
|
+
const request = await fetch(input, init);
|
|
15
|
+
if (!request.ok) {
|
|
16
|
+
const errorText = await request.text();
|
|
17
|
+
throw new Error(`API Error (${request.status}): ${errorText}`);
|
|
18
|
+
}
|
|
19
|
+
const json = await request.json();
|
|
20
|
+
return (json.result?.data?.json?.result || json);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns base64 encoded image
|
|
24
|
+
*
|
|
25
|
+
* @param imagePath Path to image file
|
|
26
|
+
* @param imageType Extension of image (if that matters)
|
|
27
|
+
*/
|
|
28
|
+
export async function imageToBase64(imagePath, imageType) {
|
|
29
|
+
if (!(imagePath?.trim?.()) || !fs.existsSync(imagePath)) {
|
|
30
|
+
throw new Error(`'${imagePath}': image not found`);
|
|
31
|
+
}
|
|
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
|
|
37
|
+
if (!Object.values(ImageExtension).includes(imageType)) {
|
|
38
|
+
throw new Error(`'${imagePath}': couldn't identify image type, please specify 'imageType'`);
|
|
39
|
+
}
|
|
40
|
+
const base64Header = `data:image/${imageType};base64,`;
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
fs.readFile(imagePath, (err, data) => {
|
|
43
|
+
if (err)
|
|
44
|
+
return reject(err);
|
|
45
|
+
resolve(base64Header + data.toString("base64"));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
package/dist/Whisk.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Media } from "./Media.js";
|
|
2
|
+
import { Project } from "./Project.js";
|
|
3
|
+
export declare class Account {
|
|
4
|
+
private cookie;
|
|
5
|
+
private authToken?;
|
|
6
|
+
private expiryDate?;
|
|
7
|
+
private userName?;
|
|
8
|
+
private userEmail?;
|
|
9
|
+
constructor(cookie: string, authToken?: string);
|
|
10
|
+
refresh(): Promise<void>;
|
|
11
|
+
getToken(): Promise<string>;
|
|
12
|
+
getCookie(): string;
|
|
13
|
+
isExpired(): boolean;
|
|
14
|
+
toString(): string;
|
|
15
|
+
}
|
|
16
|
+
export declare class Whisk {
|
|
17
|
+
readonly account: Account;
|
|
18
|
+
constructor(cookie: string, authToken?: string);
|
|
19
|
+
/**
|
|
20
|
+
* Delete a generated media - image, video
|
|
21
|
+
*
|
|
22
|
+
* @param mediaId Media id or list of ids to delete
|
|
23
|
+
* @param account Account{} object
|
|
24
|
+
*/
|
|
25
|
+
static deleteMedia(mediaId: string | string[], account: Account): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Generate caption from provided base64 image
|
|
28
|
+
*
|
|
29
|
+
* @param input base64 encoded image
|
|
30
|
+
* @param account Account{} object
|
|
31
|
+
* @param count Number of captions to generate (min: 0, max: 8)
|
|
32
|
+
*/
|
|
33
|
+
static generateCaption(input: string, account: Account, count?: number): Promise<string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Tries to get media from their unique id
|
|
36
|
+
*
|
|
37
|
+
* @param mediaId Unique identifier for generated media `mediaGenerationId`
|
|
38
|
+
*/
|
|
39
|
+
static getMedia(mediaId: string, account: Account): Promise<Media>;
|
|
40
|
+
/**
|
|
41
|
+
* Create a new project for your AI slop
|
|
42
|
+
*
|
|
43
|
+
* @param projectName Name of the project
|
|
44
|
+
*/
|
|
45
|
+
newProject(projectName?: string): Promise<Project>;
|
|
46
|
+
}
|
package/dist/Whisk.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Media } from "./Media.js";
|
|
2
|
+
import { Project } from "./Project.js";
|
|
3
|
+
import { request } from "./Utils.js";
|
|
4
|
+
export class Account {
|
|
5
|
+
cookie;
|
|
6
|
+
authToken;
|
|
7
|
+
expiryDate;
|
|
8
|
+
userName;
|
|
9
|
+
userEmail;
|
|
10
|
+
constructor(cookie, authToken) {
|
|
11
|
+
if (typeof cookie !== 'string' || !cookie.trim()) {
|
|
12
|
+
throw new Error(`'${cookie}': is not a valid cookie`);
|
|
13
|
+
}
|
|
14
|
+
// If someone provided token but its not a string
|
|
15
|
+
if (authToken && typeof authToken !== 'string') {
|
|
16
|
+
throw new Error(`${authToken}: is not a valid auth token`);
|
|
17
|
+
}
|
|
18
|
+
this.cookie = cookie;
|
|
19
|
+
this.authToken = authToken?.trim() || undefined;
|
|
20
|
+
// Assume it expires in 3 hours
|
|
21
|
+
this.expiryDate = new Date(Date.now() + 10800000);
|
|
22
|
+
}
|
|
23
|
+
async refresh() {
|
|
24
|
+
const session = await request("https://labs.google/fx/api/auth/session", { headers: { cookie: this.cookie } });
|
|
25
|
+
if (session.error === "ACCESS_TOKEN_REFRESH_NEEDED") {
|
|
26
|
+
throw new Error("new cookie is required");
|
|
27
|
+
}
|
|
28
|
+
this.authToken = session.access_token;
|
|
29
|
+
this.expiryDate = new Date(session.expires);
|
|
30
|
+
this.userName = session.user.name;
|
|
31
|
+
this.userEmail = session.user.email;
|
|
32
|
+
}
|
|
33
|
+
async getToken() {
|
|
34
|
+
if (this.isExpired()) {
|
|
35
|
+
await this.refresh();
|
|
36
|
+
}
|
|
37
|
+
return this.authToken;
|
|
38
|
+
}
|
|
39
|
+
getCookie() {
|
|
40
|
+
return this.cookie;
|
|
41
|
+
}
|
|
42
|
+
isExpired() {
|
|
43
|
+
if (!this.authToken || !this.expiryDate || !(this.expiryDate instanceof Date)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// 30 second to prevent mid-request token expiry
|
|
47
|
+
return Date.now() + 15 > this.expiryDate.getTime();
|
|
48
|
+
}
|
|
49
|
+
toString() {
|
|
50
|
+
if (this.isExpired()) {
|
|
51
|
+
return "No account found, might need a refresh.";
|
|
52
|
+
}
|
|
53
|
+
return "Username: " + this.userName + "\n" +
|
|
54
|
+
"Email: " + this.userEmail + "\n" +
|
|
55
|
+
"Cookie: " + this.cookie.slice(0, 5) + "*".repeat(10) + "\n" +
|
|
56
|
+
"Auth Token: " + this.authToken.slice(0, 5) + "*".repeat(10);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export class Whisk {
|
|
60
|
+
account;
|
|
61
|
+
constructor(cookie, authToken) {
|
|
62
|
+
this.account = new Account(cookie, authToken);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Delete a generated media - image, video
|
|
66
|
+
*
|
|
67
|
+
* @param mediaId Media id or list of ids to delete
|
|
68
|
+
* @param account Account{} object
|
|
69
|
+
*/
|
|
70
|
+
static async deleteMedia(mediaId, account) {
|
|
71
|
+
if (typeof mediaId === "string") {
|
|
72
|
+
mediaId = [mediaId];
|
|
73
|
+
}
|
|
74
|
+
if (!(account instanceof Account)) {
|
|
75
|
+
throw new Error("invalid or missing account");
|
|
76
|
+
}
|
|
77
|
+
await request("https://labs.google/fx/api/trpc/media.deleteMedia", {
|
|
78
|
+
headers: { cookie: account.getCookie() },
|
|
79
|
+
body: JSON.stringify({ "json": { "names": mediaId } })
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate caption from provided base64 image
|
|
84
|
+
*
|
|
85
|
+
* @param input base64 encoded image
|
|
86
|
+
* @param account Account{} object
|
|
87
|
+
* @param count Number of captions to generate (min: 0, max: 8)
|
|
88
|
+
*/
|
|
89
|
+
static async generateCaption(input, account, count = 1) {
|
|
90
|
+
if (!(input?.trim?.())) {
|
|
91
|
+
throw new Error("input image or media id is required");
|
|
92
|
+
}
|
|
93
|
+
if (count <= 0 || count > 8) {
|
|
94
|
+
throw new Error("count must be in between 0 and 9 (0 < count < 9)");
|
|
95
|
+
}
|
|
96
|
+
if (!(account instanceof Account)) {
|
|
97
|
+
throw new Error("invalid or missing account");
|
|
98
|
+
}
|
|
99
|
+
const captionResults = await request("https://labs.google/fx/api/trpc/backbone.captionImage", {
|
|
100
|
+
headers: { cookie: account.getCookie() },
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
"json": {
|
|
103
|
+
"captionInput": {
|
|
104
|
+
"candidatesCount": count,
|
|
105
|
+
"mediaInput": {
|
|
106
|
+
"rawBytes": input,
|
|
107
|
+
"mediaCategory": "MEDIA_CATEGORY_SUBJECT"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
return captionResults.candidates.map(item => item.output);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Tries to get media from their unique id
|
|
117
|
+
*
|
|
118
|
+
* @param mediaId Unique identifier for generated media `mediaGenerationId`
|
|
119
|
+
*/
|
|
120
|
+
static async getMedia(mediaId, account) {
|
|
121
|
+
if (typeof mediaId !== "string" || !mediaId.trim()) {
|
|
122
|
+
throw new Error("invalid or missing media id");
|
|
123
|
+
}
|
|
124
|
+
if (!(account instanceof Account)) {
|
|
125
|
+
throw new Error("invalid or missing account");
|
|
126
|
+
}
|
|
127
|
+
const mediaInfo = await request(
|
|
128
|
+
// key is hardcoded in the original code too
|
|
129
|
+
`https://aisandbox-pa.googleapis.com/v1/media/${mediaId}?key=AIzaSyBtrm0o5ab1c-Ec8ZuLcGt3oJAA5VWt3pY`, {
|
|
130
|
+
headers: {
|
|
131
|
+
"Referer": "https://labs.google/", // yep this ones required
|
|
132
|
+
"Authorization": `Bearer ${await account.getToken()}`,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
const media = mediaInfo.image ?? mediaInfo.video;
|
|
136
|
+
return new Media({
|
|
137
|
+
seed: media.seed,
|
|
138
|
+
prompt: media.prompt,
|
|
139
|
+
workflowId: media.workflowId,
|
|
140
|
+
encodedMedia: media.encodedImage ?? media.encodedVideo,
|
|
141
|
+
mediaGenerationId: media.mediaGenerationId,
|
|
142
|
+
aspectRatio: media.aspectRatio,
|
|
143
|
+
mediaType: mediaInfo.mediaGenerationId.mediaType,
|
|
144
|
+
model: media.modelNameType ?? media.model,
|
|
145
|
+
account: account,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Create a new project for your AI slop
|
|
150
|
+
*
|
|
151
|
+
* @param projectName Name of the project
|
|
152
|
+
*/
|
|
153
|
+
async newProject(projectName) {
|
|
154
|
+
if (typeof projectName !== "string" || !projectName.trim()) {
|
|
155
|
+
projectName = "New Project - " + (new Date().toDateString().replace(/\s/g, "-"));
|
|
156
|
+
}
|
|
157
|
+
const projectInfo = await request("https://labs.google/fx/api/trpc/media.createOrUpdateWorkflow", {
|
|
158
|
+
headers: { cookie: this.account.getCookie() },
|
|
159
|
+
body: JSON.stringify({
|
|
160
|
+
"json": { "workflowMetadata": { "workflowName": projectName } }
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
return new Project(projectInfo.workflowId, this.account);
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rohitaryal/whisk-api",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist"
|
|
9
9
|
],
|
|
10
|
+
"exports": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
10
14
|
"private": false,
|
|
11
15
|
"scripts": {
|
|
12
16
|
"test": "bun test --concurrent",
|
|
13
|
-
"build": "tsc && chmod +x ./dist/
|
|
17
|
+
"build": "tsc && chmod +x ./dist/Cli.js"
|
|
14
18
|
},
|
|
15
19
|
"devDependencies": {
|
|
16
20
|
"@types/bun": "^1.3.5",
|
|
@@ -44,9 +48,5 @@
|
|
|
44
48
|
"url": "https://github.com/rohitaryal/whisk-api/issues"
|
|
45
49
|
},
|
|
46
50
|
"homepage": "https://github.com/rohitaryal/whisk-api#readme",
|
|
47
|
-
"directories": {
|
|
48
|
-
"example": "examples",
|
|
49
|
-
"test": "tests"
|
|
50
|
-
},
|
|
51
51
|
"description": "Unofficial API for Whisk image generation."
|
|
52
52
|
}
|