climage 0.5.0 → 0.5.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.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
  }
@@ -700,6 +703,21 @@ function imageToGoogleFormat(imageInput) {
700
703
  }
701
704
  return { fileUri: imageInput };
702
705
  }
706
+ function imageToVeoFormat(imageInput) {
707
+ if (imageInput.startsWith("data:")) {
708
+ const parsed = parseDataUri(imageInput);
709
+ if (!parsed?.data) {
710
+ throw new Error("Failed to parse data URI for Veo image input");
711
+ }
712
+ return { imageBytes: parsed.data, mimeType: parsed.mimeType };
713
+ }
714
+ if (imageInput.startsWith("gs://")) {
715
+ return { gcsUri: imageInput };
716
+ }
717
+ throw new Error(
718
+ `Veo image inputs must be data: URIs or gs:// URIs (got ${imageInput.slice(0, 24)}...)`
719
+ );
720
+ }
703
721
  var GEMINI_IMAGE_MODELS = ["gemini-2.5-flash-image", "gemini-3-pro-image-preview"];
704
722
  function resolveModel(model) {
705
723
  if (!model) return "gemini-2.5-flash-image";
@@ -718,6 +736,31 @@ async function downloadBytes3(url) {
718
736
  log3(`Downloaded ${ab.byteLength} bytes in ${Date.now() - start}ms, type: ${ct}`);
719
737
  return { bytes: new Uint8Array(ab), mimeType: ct };
720
738
  }
739
+ async function downloadGeneratedVideo(ai, generatedVideo) {
740
+ const video = generatedVideo?.video;
741
+ if (video?.videoBytes) {
742
+ return {
743
+ bytes: new Uint8Array(Buffer.from(video.videoBytes, "base64")),
744
+ mimeType: video.mimeType
745
+ };
746
+ }
747
+ if (video?.uri && !video.uri.startsWith("gs://")) {
748
+ try {
749
+ return await downloadBytes3(video.uri);
750
+ } catch (err) {
751
+ log3("Direct video download failed, falling back to ai.files.download:", String(err));
752
+ }
753
+ }
754
+ const tempDir = await mkdtemp(join(tmpdir(), "climage-veo-"));
755
+ const downloadPath = join(tempDir, "video.mp4");
756
+ try {
757
+ await ai.files.download({ file: generatedVideo, downloadPath });
758
+ const buf = await readFile(downloadPath);
759
+ return { bytes: new Uint8Array(buf), mimeType: video?.mimeType ?? "video/mp4" };
760
+ } finally {
761
+ await rm(tempDir, { recursive: true, force: true });
762
+ }
763
+ }
721
764
  async function sleep2(ms) {
722
765
  await new Promise((r) => setTimeout(r, ms));
723
766
  }
@@ -783,15 +826,15 @@ async function generateWithVeo(ai, model, req) {
783
826
  const config = {
784
827
  numberOfVideos: req.n,
785
828
  ...req.aspectRatio ? { aspectRatio: req.aspectRatio } : {},
786
- // Add duration if specified (Veo 3.1 supports 4, 6, 8)
787
- ...req.duration !== void 0 ? { durationSeconds: String(req.duration) } : {}
829
+ // Add duration if specified (Veo supports 4-8 seconds depending on model)
830
+ ...req.duration !== void 0 ? { durationSeconds: req.duration } : {}
788
831
  };
789
832
  if (req.inputImages?.length && isVeo31Model(model)) {
790
833
  const referenceImages = req.inputImages.slice(0, 3).map((img) => {
791
- const imageData = imageToGoogleFormat(img);
834
+ const imageData = imageToVeoFormat(img);
792
835
  return {
793
836
  image: imageData,
794
- referenceType: "asset"
837
+ referenceType: "ASSET"
795
838
  };
796
839
  });
797
840
  config.referenceImages = referenceImages;
@@ -804,12 +847,12 @@ async function generateWithVeo(ai, model, req) {
804
847
  };
805
848
  const firstFrameImage = req.startFrame ?? (req.inputImages?.length === 1 ? req.inputImages[0] : void 0);
806
849
  if (firstFrameImage && isVeo31Model(model)) {
807
- const imageData = imageToGoogleFormat(firstFrameImage);
850
+ const imageData = imageToVeoFormat(firstFrameImage);
808
851
  generateParams.image = imageData;
809
852
  log3("Added first frame image");
810
853
  }
811
854
  if (req.endFrame && isVeo31Model(model)) {
812
- const lastFrameData = imageToGoogleFormat(req.endFrame);
855
+ const lastFrameData = imageToVeoFormat(req.endFrame);
813
856
  config.lastFrame = lastFrameData;
814
857
  log3("Added last frame for interpolation");
815
858
  }
@@ -839,26 +882,22 @@ async function generateWithVeo(ai, model, req) {
839
882
  for (let i = 0; i < Math.min(videos.length, req.n); i++) {
840
883
  const v = videos[i];
841
884
  log3(`Processing video ${i}:`, JSON.stringify(v).slice(0, 300));
842
- const uri = v?.video?.uri;
843
- if (!uri) {
844
- log3(`Video ${i} has no URI, skipping`);
885
+ if (!v?.video) {
886
+ log3(`Video ${i} has no video payload, skipping`);
845
887
  continue;
846
888
  }
847
- if (uri.startsWith("gs://")) {
848
- throw new Error(
849
- `Google Veo returned a gs:// URI (${uri}). Configure outputGcsUri / Vertex flow to fetch from GCS.`
850
- );
851
- }
852
- const { bytes, mimeType } = await downloadBytes3(uri);
853
- out.push({
889
+ const uri = v?.video?.uri;
890
+ const { bytes, mimeType } = await downloadGeneratedVideo(ai, v);
891
+ const item = {
854
892
  kind: "video",
855
893
  provider: "google",
856
894
  model,
857
895
  index: i,
858
- url: uri,
859
896
  bytes,
860
897
  ...mimeType !== void 0 ? { mimeType } : {}
861
- });
898
+ };
899
+ if (uri) item.url = uri;
900
+ out.push(item);
862
901
  }
863
902
  if (!out.length) throw new Error("Google Veo returned videos but none were downloadable");
864
903
  log3(`Successfully generated ${out.length} video(s)`);