climage 0.5.0 → 0.5.2
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 +8 -7
- package/dist/cli.js +63 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.js +63 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,13 +23,14 @@ Set one of:
|
|
|
23
23
|
|
|
24
24
|
**Models:**
|
|
25
25
|
|
|
26
|
-
| Model
|
|
27
|
-
|
|
|
28
|
-
| `gemini-3-pro-image-preview`
|
|
29
|
-
| `gemini-
|
|
30
|
-
| `
|
|
31
|
-
| `imagen-4.0-
|
|
32
|
-
| `imagen-4.0-
|
|
26
|
+
| Model | Alias | Description |
|
|
27
|
+
| -------------------------------- | ----------------- | ------------------------------------------------------------------------------------- |
|
|
28
|
+
| `gemini-3-pro-image-preview` | `nano-banana-pro` | **Default.** State-of-the-art, professional asset production, up to 4K, thinking mode |
|
|
29
|
+
| `gemini-3.1-flash-image-preview` | `nano-banana-2` | Nano Banana 2 — fast native image generation |
|
|
30
|
+
| `gemini-2.5-flash-image` | `nano-banana` | Fast & efficient, optimized for high-volume tasks |
|
|
31
|
+
| `imagen-4.0-generate-001` | - | Imagen 4 Standard |
|
|
32
|
+
| `imagen-4.0-ultra-generate-001` | - | Imagen 4 Ultra (best quality) |
|
|
33
|
+
| `imagen-4.0-fast-generate-001` | - | Imagen 4 Fast |
|
|
33
34
|
|
|
34
35
|
Example:
|
|
35
36
|
|
package/dist/cli.js
CHANGED
|
@@ -653,6 +653,9 @@ var falProvider = {
|
|
|
653
653
|
|
|
654
654
|
// src/providers/google.ts
|
|
655
655
|
import { GoogleGenAI } from "@google/genai";
|
|
656
|
+
import { mkdtemp, readFile, rm } from "fs/promises";
|
|
657
|
+
import { tmpdir } from "os";
|
|
658
|
+
import { join } from "path";
|
|
656
659
|
function getGeminiApiKey(env) {
|
|
657
660
|
return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY;
|
|
658
661
|
}
|
|
@@ -674,6 +677,7 @@ function log3(...args) {
|
|
|
674
677
|
var MODEL_ALIASES = {
|
|
675
678
|
"nano-banana": "gemini-2.5-flash-image",
|
|
676
679
|
"nano-banana-pro": "gemini-3-pro-image-preview",
|
|
680
|
+
"nano-banana-2": "gemini-3.1-flash-image-preview",
|
|
677
681
|
// Veo (video)
|
|
678
682
|
veo2: "veo-2.0-generate-001",
|
|
679
683
|
"veo-2": "veo-2.0-generate-001",
|
|
@@ -700,7 +704,26 @@ function imageToGoogleFormat(imageInput) {
|
|
|
700
704
|
}
|
|
701
705
|
return { fileUri: imageInput };
|
|
702
706
|
}
|
|
703
|
-
|
|
707
|
+
function imageToVeoFormat(imageInput) {
|
|
708
|
+
if (imageInput.startsWith("data:")) {
|
|
709
|
+
const parsed = parseDataUri(imageInput);
|
|
710
|
+
if (!parsed?.data) {
|
|
711
|
+
throw new Error("Failed to parse data URI for Veo image input");
|
|
712
|
+
}
|
|
713
|
+
return { imageBytes: parsed.data, mimeType: parsed.mimeType };
|
|
714
|
+
}
|
|
715
|
+
if (imageInput.startsWith("gs://")) {
|
|
716
|
+
return { gcsUri: imageInput };
|
|
717
|
+
}
|
|
718
|
+
throw new Error(
|
|
719
|
+
`Veo image inputs must be data: URIs or gs:// URIs (got ${imageInput.slice(0, 24)}...)`
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
var GEMINI_IMAGE_MODELS = [
|
|
723
|
+
"gemini-2.5-flash-image",
|
|
724
|
+
"gemini-3-pro-image-preview",
|
|
725
|
+
"gemini-3.1-flash-image-preview"
|
|
726
|
+
];
|
|
704
727
|
function resolveModel(model) {
|
|
705
728
|
if (!model) return "gemini-2.5-flash-image";
|
|
706
729
|
return MODEL_ALIASES[model] ?? model;
|
|
@@ -718,6 +741,31 @@ async function downloadBytes3(url) {
|
|
|
718
741
|
log3(`Downloaded ${ab.byteLength} bytes in ${Date.now() - start}ms, type: ${ct}`);
|
|
719
742
|
return { bytes: new Uint8Array(ab), mimeType: ct };
|
|
720
743
|
}
|
|
744
|
+
async function downloadGeneratedVideo(ai, generatedVideo) {
|
|
745
|
+
const video = generatedVideo?.video;
|
|
746
|
+
if (video?.videoBytes) {
|
|
747
|
+
return {
|
|
748
|
+
bytes: new Uint8Array(Buffer.from(video.videoBytes, "base64")),
|
|
749
|
+
mimeType: video.mimeType
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
if (video?.uri && !video.uri.startsWith("gs://")) {
|
|
753
|
+
try {
|
|
754
|
+
return await downloadBytes3(video.uri);
|
|
755
|
+
} catch (err) {
|
|
756
|
+
log3("Direct video download failed, falling back to ai.files.download:", String(err));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const tempDir = await mkdtemp(join(tmpdir(), "climage-veo-"));
|
|
760
|
+
const downloadPath = join(tempDir, "video.mp4");
|
|
761
|
+
try {
|
|
762
|
+
await ai.files.download({ file: generatedVideo, downloadPath });
|
|
763
|
+
const buf = await readFile(downloadPath);
|
|
764
|
+
return { bytes: new Uint8Array(buf), mimeType: video?.mimeType ?? "video/mp4" };
|
|
765
|
+
} finally {
|
|
766
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
721
769
|
async function sleep2(ms) {
|
|
722
770
|
await new Promise((r) => setTimeout(r, ms));
|
|
723
771
|
}
|
|
@@ -783,15 +831,15 @@ async function generateWithVeo(ai, model, req) {
|
|
|
783
831
|
const config = {
|
|
784
832
|
numberOfVideos: req.n,
|
|
785
833
|
...req.aspectRatio ? { aspectRatio: req.aspectRatio } : {},
|
|
786
|
-
// Add duration if specified (Veo
|
|
787
|
-
...req.duration !== void 0 ? { durationSeconds:
|
|
834
|
+
// Add duration if specified (Veo supports 4-8 seconds depending on model)
|
|
835
|
+
...req.duration !== void 0 ? { durationSeconds: req.duration } : {}
|
|
788
836
|
};
|
|
789
837
|
if (req.inputImages?.length && isVeo31Model(model)) {
|
|
790
838
|
const referenceImages = req.inputImages.slice(0, 3).map((img) => {
|
|
791
|
-
const imageData =
|
|
839
|
+
const imageData = imageToVeoFormat(img);
|
|
792
840
|
return {
|
|
793
841
|
image: imageData,
|
|
794
|
-
referenceType: "
|
|
842
|
+
referenceType: "ASSET"
|
|
795
843
|
};
|
|
796
844
|
});
|
|
797
845
|
config.referenceImages = referenceImages;
|
|
@@ -804,12 +852,12 @@ async function generateWithVeo(ai, model, req) {
|
|
|
804
852
|
};
|
|
805
853
|
const firstFrameImage = req.startFrame ?? (req.inputImages?.length === 1 ? req.inputImages[0] : void 0);
|
|
806
854
|
if (firstFrameImage && isVeo31Model(model)) {
|
|
807
|
-
const imageData =
|
|
855
|
+
const imageData = imageToVeoFormat(firstFrameImage);
|
|
808
856
|
generateParams.image = imageData;
|
|
809
857
|
log3("Added first frame image");
|
|
810
858
|
}
|
|
811
859
|
if (req.endFrame && isVeo31Model(model)) {
|
|
812
|
-
const lastFrameData =
|
|
860
|
+
const lastFrameData = imageToVeoFormat(req.endFrame);
|
|
813
861
|
config.lastFrame = lastFrameData;
|
|
814
862
|
log3("Added last frame for interpolation");
|
|
815
863
|
}
|
|
@@ -839,26 +887,22 @@ async function generateWithVeo(ai, model, req) {
|
|
|
839
887
|
for (let i = 0; i < Math.min(videos.length, req.n); i++) {
|
|
840
888
|
const v = videos[i];
|
|
841
889
|
log3(`Processing video ${i}:`, JSON.stringify(v).slice(0, 300));
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
log3(`Video ${i} has no URI, skipping`);
|
|
890
|
+
if (!v?.video) {
|
|
891
|
+
log3(`Video ${i} has no video payload, skipping`);
|
|
845
892
|
continue;
|
|
846
893
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
);
|
|
851
|
-
}
|
|
852
|
-
const { bytes, mimeType } = await downloadBytes3(uri);
|
|
853
|
-
out.push({
|
|
894
|
+
const uri = v?.video?.uri;
|
|
895
|
+
const { bytes, mimeType } = await downloadGeneratedVideo(ai, v);
|
|
896
|
+
const item = {
|
|
854
897
|
kind: "video",
|
|
855
898
|
provider: "google",
|
|
856
899
|
model,
|
|
857
900
|
index: i,
|
|
858
|
-
url: uri,
|
|
859
901
|
bytes,
|
|
860
902
|
...mimeType !== void 0 ? { mimeType } : {}
|
|
861
|
-
}
|
|
903
|
+
};
|
|
904
|
+
if (uri) item.url = uri;
|
|
905
|
+
out.push(item);
|
|
862
906
|
}
|
|
863
907
|
if (!out.length) throw new Error("Google Veo returned videos but none were downloadable");
|
|
864
908
|
log3(`Successfully generated ${out.length} video(s)`);
|