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/index.js CHANGED
@@ -624,6 +624,9 @@ var falProvider = {
624
624
 
625
625
  // src/providers/google.ts
626
626
  import { GoogleGenAI } from "@google/genai";
627
+ import { mkdtemp, readFile, rm } from "fs/promises";
628
+ import { tmpdir } from "os";
629
+ import { join } from "path";
627
630
  function getGeminiApiKey(env) {
628
631
  return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY;
629
632
  }
@@ -671,6 +674,21 @@ function imageToGoogleFormat(imageInput) {
671
674
  }
672
675
  return { fileUri: imageInput };
673
676
  }
677
+ function imageToVeoFormat(imageInput) {
678
+ if (imageInput.startsWith("data:")) {
679
+ const parsed = parseDataUri(imageInput);
680
+ if (!parsed?.data) {
681
+ throw new Error("Failed to parse data URI for Veo image input");
682
+ }
683
+ return { imageBytes: parsed.data, mimeType: parsed.mimeType };
684
+ }
685
+ if (imageInput.startsWith("gs://")) {
686
+ return { gcsUri: imageInput };
687
+ }
688
+ throw new Error(
689
+ `Veo image inputs must be data: URIs or gs:// URIs (got ${imageInput.slice(0, 24)}...)`
690
+ );
691
+ }
674
692
  var GEMINI_IMAGE_MODELS = ["gemini-2.5-flash-image", "gemini-3-pro-image-preview"];
675
693
  function resolveModel(model) {
676
694
  if (!model) return "gemini-2.5-flash-image";
@@ -689,6 +707,31 @@ async function downloadBytes3(url) {
689
707
  log3(`Downloaded ${ab.byteLength} bytes in ${Date.now() - start}ms, type: ${ct}`);
690
708
  return { bytes: new Uint8Array(ab), mimeType: ct };
691
709
  }
710
+ async function downloadGeneratedVideo(ai, generatedVideo) {
711
+ const video = generatedVideo?.video;
712
+ if (video?.videoBytes) {
713
+ return {
714
+ bytes: new Uint8Array(Buffer.from(video.videoBytes, "base64")),
715
+ mimeType: video.mimeType
716
+ };
717
+ }
718
+ if (video?.uri && !video.uri.startsWith("gs://")) {
719
+ try {
720
+ return await downloadBytes3(video.uri);
721
+ } catch (err) {
722
+ log3("Direct video download failed, falling back to ai.files.download:", String(err));
723
+ }
724
+ }
725
+ const tempDir = await mkdtemp(join(tmpdir(), "climage-veo-"));
726
+ const downloadPath = join(tempDir, "video.mp4");
727
+ try {
728
+ await ai.files.download({ file: generatedVideo, downloadPath });
729
+ const buf = await readFile(downloadPath);
730
+ return { bytes: new Uint8Array(buf), mimeType: video?.mimeType ?? "video/mp4" };
731
+ } finally {
732
+ await rm(tempDir, { recursive: true, force: true });
733
+ }
734
+ }
692
735
  async function sleep2(ms) {
693
736
  await new Promise((r) => setTimeout(r, ms));
694
737
  }
@@ -754,15 +797,15 @@ async function generateWithVeo(ai, model, req) {
754
797
  const config = {
755
798
  numberOfVideos: req.n,
756
799
  ...req.aspectRatio ? { aspectRatio: req.aspectRatio } : {},
757
- // Add duration if specified (Veo 3.1 supports 4, 6, 8)
758
- ...req.duration !== void 0 ? { durationSeconds: String(req.duration) } : {}
800
+ // Add duration if specified (Veo supports 4-8 seconds depending on model)
801
+ ...req.duration !== void 0 ? { durationSeconds: req.duration } : {}
759
802
  };
760
803
  if (req.inputImages?.length && isVeo31Model(model)) {
761
804
  const referenceImages = req.inputImages.slice(0, 3).map((img) => {
762
- const imageData = imageToGoogleFormat(img);
805
+ const imageData = imageToVeoFormat(img);
763
806
  return {
764
807
  image: imageData,
765
- referenceType: "asset"
808
+ referenceType: "ASSET"
766
809
  };
767
810
  });
768
811
  config.referenceImages = referenceImages;
@@ -775,12 +818,12 @@ async function generateWithVeo(ai, model, req) {
775
818
  };
776
819
  const firstFrameImage = req.startFrame ?? (req.inputImages?.length === 1 ? req.inputImages[0] : void 0);
777
820
  if (firstFrameImage && isVeo31Model(model)) {
778
- const imageData = imageToGoogleFormat(firstFrameImage);
821
+ const imageData = imageToVeoFormat(firstFrameImage);
779
822
  generateParams.image = imageData;
780
823
  log3("Added first frame image");
781
824
  }
782
825
  if (req.endFrame && isVeo31Model(model)) {
783
- const lastFrameData = imageToGoogleFormat(req.endFrame);
826
+ const lastFrameData = imageToVeoFormat(req.endFrame);
784
827
  config.lastFrame = lastFrameData;
785
828
  log3("Added last frame for interpolation");
786
829
  }
@@ -810,26 +853,22 @@ async function generateWithVeo(ai, model, req) {
810
853
  for (let i = 0; i < Math.min(videos.length, req.n); i++) {
811
854
  const v = videos[i];
812
855
  log3(`Processing video ${i}:`, JSON.stringify(v).slice(0, 300));
813
- const uri = v?.video?.uri;
814
- if (!uri) {
815
- log3(`Video ${i} has no URI, skipping`);
856
+ if (!v?.video) {
857
+ log3(`Video ${i} has no video payload, skipping`);
816
858
  continue;
817
859
  }
818
- if (uri.startsWith("gs://")) {
819
- throw new Error(
820
- `Google Veo returned a gs:// URI (${uri}). Configure outputGcsUri / Vertex flow to fetch from GCS.`
821
- );
822
- }
823
- const { bytes, mimeType } = await downloadBytes3(uri);
824
- out.push({
860
+ const uri = v?.video?.uri;
861
+ const { bytes, mimeType } = await downloadGeneratedVideo(ai, v);
862
+ const item = {
825
863
  kind: "video",
826
864
  provider: "google",
827
865
  model,
828
866
  index: i,
829
- url: uri,
830
867
  bytes,
831
868
  ...mimeType !== void 0 ? { mimeType } : {}
832
- });
869
+ };
870
+ if (uri) item.url = uri;
871
+ out.push(item);
833
872
  }
834
873
  if (!out.length) throw new Error("Google Veo returned videos but none were downloadable");
835
874
  log3(`Successfully generated ${out.length} video(s)`);