climage 0.4.1 → 0.5.0

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
@@ -100,11 +100,12 @@ Set one of:
100
100
 
101
101
  **Models:**
102
102
 
103
- | Model | Description |
104
- | --------------------- | -------------------------------------- |
105
- | `fal-ai/flux/dev` | **Default.** Flux dev (fast & popular) |
106
- | `fal-ai/flux/pro` | Flux pro (higher quality) |
107
- | `fal-ai/flux-realism` | Photorealistic style |
103
+ | Model | Description |
104
+ | ------------------------------------------ | -------------------------------------- |
105
+ | `fal-ai/flux/dev` | **Default.** Flux dev (fast & popular) |
106
+ | `fal-ai/flux/pro` | Flux pro (higher quality) |
107
+ | `fal-ai/flux-realism` | Photorealistic style |
108
+ | `fal-ai/kling-video/v3/pro/image-to-video` | Kling v3 Pro image-to-video |
108
109
 
109
110
  Example:
110
111
 
@@ -166,6 +167,9 @@ npx climage "the scene comes to life" --video --provider google --start-frame sc
166
167
  # Image-to-video with fal.ai
167
168
  npx climage "dramatic camera zoom" --video --provider fal --start-frame photo.jpg
168
169
 
170
+ # Image-to-video with fal.ai Kling v3 Pro
171
+ npx climage "dramatic camera zoom" --video --provider fal --model fal-ai/kling-video/v3/pro/image-to-video --start-frame photo.jpg
172
+
169
173
  # Image-to-video with xAI
170
174
  npx climage "animate this scene" --video --provider xai --start-frame cat.png
171
175
  ```
@@ -204,7 +208,9 @@ npx climage "character in motion" --video --provider fal --input ref1.png --inpu
204
208
  | Image-to-Video | Yes | Yes | Yes | No |
205
209
  | Video Interpolation | Yes | No | Yes | No |
206
210
  | Max Input Images | 3 | 1 | 7 | 2 |
207
- | Video Duration (seconds) | 4-8 | 1-15 | 2-8 | N/A |
211
+ | Video Duration (seconds) | 4-8 | 1-15 | 2-15\* | N/A |
212
+
213
+ \* Model-specific on fal.ai (e.g. Vidu: 2-8, Kling v3 Pro: 3-15).
208
214
 
209
215
  ## Library API
210
216
 
package/dist/cli.js CHANGED
@@ -285,8 +285,7 @@ async function generateXaiVideo(req, apiKey) {
285
285
  prompt: req.prompt,
286
286
  model,
287
287
  ...req.aspectRatio ? { aspect_ratio: req.aspectRatio } : {},
288
- // Add image_url for image-to-video (data URI or URL string)
289
- ...imageUrl ? { image_url: imageUrl } : {},
288
+ ...imageUrl ? { image: { url: imageUrl } } : {},
290
289
  // Add duration (xAI supports 1-15 seconds)
291
290
  ...req.duration !== void 0 ? { duration: req.duration } : {}
292
291
  };
@@ -294,7 +293,7 @@ async function generateXaiVideo(req, apiKey) {
294
293
  "Request body:",
295
294
  JSON.stringify({
296
295
  ...createBody,
297
- image_url: createBody.image_url ? `...(${String(createBody.image_url).length} chars)` : void 0
296
+ image: createBody.image ? { url: `...(${String(createBody.image.url).length} chars)` } : void 0
298
297
  })
299
298
  );
300
299
  log("Calling xAI videos/generations...");
@@ -438,8 +437,15 @@ var DEFAULT_IMAGE_MODEL = "fal-ai/flux/dev";
438
437
  var DEFAULT_IMAGE_TO_IMAGE_MODEL = "fal-ai/flux/dev/image-to-image";
439
438
  var DEFAULT_VIDEO_MODEL = "fal-ai/ltxv-2/text-to-video/fast";
440
439
  var DEFAULT_IMAGE_TO_VIDEO_MODEL = "fal-ai/vidu/q2/image-to-video";
440
+ var KLING_V3_PRO_IMAGE_TO_VIDEO_MODEL = "fal-ai/kling-video/v3/pro/image-to-video";
441
441
  var DEFAULT_START_END_VIDEO_MODEL = "fal-ai/vidu/start-end-to-video";
442
442
  var DEFAULT_REFERENCE_VIDEO_MODEL = "fal-ai/vidu/q2/reference-to-video";
443
+ function isKlingV3Model(model) {
444
+ return model === KLING_V3_PRO_IMAGE_TO_VIDEO_MODEL || model.startsWith("fal-ai/kling-video/v3/");
445
+ }
446
+ function isViduModel(model) {
447
+ return model.includes("/vidu/");
448
+ }
443
449
  function selectVideoModel(req) {
444
450
  if (req.model) return req.model;
445
451
  if (req.startFrame && req.endFrame) {
@@ -458,9 +464,12 @@ function selectImageModel(req) {
458
464
  if (req.inputImages?.length) return DEFAULT_IMAGE_TO_IMAGE_MODEL;
459
465
  return DEFAULT_IMAGE_MODEL;
460
466
  }
461
- function mapAspectRatio(aspectRatio) {
467
+ function mapAspectRatio(aspectRatio, model) {
462
468
  if (!aspectRatio) return void 0;
463
469
  const ar = aspectRatio.trim();
470
+ if (model && isKlingV3Model(model)) {
471
+ return ar;
472
+ }
464
473
  if (ar === "1:1") return "square";
465
474
  if (ar === "4:3") return "landscape_4_3";
466
475
  if (ar === "16:9") return "landscape_16_9";
@@ -468,29 +477,50 @@ function mapAspectRatio(aspectRatio) {
468
477
  if (ar === "9:16") return "portrait_16_9";
469
478
  return ar;
470
479
  }
471
- function buildVideoInput(req) {
480
+ function buildVideoInput(req, model) {
472
481
  const input = {
473
482
  prompt: req.prompt
474
483
  };
475
484
  if (req.startFrame && req.endFrame) {
476
485
  input.start_image_url = req.startFrame;
477
486
  input.end_image_url = req.endFrame;
487
+ const ar = mapAspectRatio(req.aspectRatio, model);
488
+ if (ar) input.aspect_ratio = ar;
489
+ if (req.duration) input.duration = String(req.duration);
478
490
  return input;
479
491
  }
480
492
  if (req.inputImages?.length && !req.startFrame) {
493
+ if (isKlingV3Model(model)) {
494
+ input.start_image_url = req.inputImages[0];
495
+ const ar2 = mapAspectRatio(req.aspectRatio, model);
496
+ if (ar2) input.aspect_ratio = ar2;
497
+ if (req.duration) input.duration = String(req.duration);
498
+ return input;
499
+ }
481
500
  input.reference_image_urls = req.inputImages.slice(0, 7);
482
- const ar = mapAspectRatio(req.aspectRatio);
501
+ const ar = mapAspectRatio(req.aspectRatio, model);
483
502
  if (ar) input.aspect_ratio = ar;
484
503
  if (req.duration) input.duration = String(req.duration);
485
504
  return input;
486
505
  }
487
506
  const imageUrl = req.startFrame ?? req.inputImages?.[0];
488
507
  if (imageUrl) {
489
- input.image_url = imageUrl;
508
+ if (isKlingV3Model(model)) {
509
+ input.start_image_url = imageUrl;
510
+ const ar = mapAspectRatio(req.aspectRatio, model);
511
+ if (ar) input.aspect_ratio = ar;
512
+ } else {
513
+ input.image_url = imageUrl;
514
+ }
490
515
  if (req.duration) input.duration = String(req.duration);
491
516
  return input;
492
517
  }
493
- const imageSize = mapAspectRatio(req.aspectRatio);
518
+ if (isKlingV3Model(model)) {
519
+ throw new Error(
520
+ `Model ${model} requires --start-frame (or --input) because it is image-to-video only`
521
+ );
522
+ }
523
+ const imageSize = mapAspectRatio(req.aspectRatio, model);
494
524
  if (imageSize) input.image_size = imageSize;
495
525
  if (req.n) input.num_videos = req.n;
496
526
  return input;
@@ -515,8 +545,8 @@ var falCapabilities = {
515
545
  supportsCustomAspectRatio: true,
516
546
  supportsVideoInterpolation: true,
517
547
  // Vidu start-end-to-video
518
- videoDurationRange: [2, 8],
519
- // Vidu supports 2-8 seconds
548
+ videoDurationRange: [2, 15],
549
+ // Most models are 2-8; Kling v3 supports up to 15
520
550
  supportsImageEditing: true
521
551
  };
522
552
  var falProvider = {
@@ -544,7 +574,19 @@ var falProvider = {
544
574
  fal.config({ credentials: key });
545
575
  const model = req.kind === "video" ? selectVideoModel(req) : selectImageModel(req);
546
576
  log2(verbose, "Selected model:", model);
547
- const input = req.kind === "video" ? buildVideoInput(req) : buildImageInput(req);
577
+ if (req.kind === "video" && req.duration !== void 0) {
578
+ if (isKlingV3Model(model) && (req.duration < 3 || req.duration > 15)) {
579
+ throw new Error(
580
+ `Model ${model} supports video duration 3-15s, but ${req.duration}s requested`
581
+ );
582
+ }
583
+ if (isViduModel(model) && (req.duration < 2 || req.duration > 8)) {
584
+ throw new Error(
585
+ `Model ${model} supports video duration 2-8s, but ${req.duration}s requested`
586
+ );
587
+ }
588
+ }
589
+ const input = req.kind === "video" ? buildVideoInput(req, model) : buildImageInput(req);
548
590
  const inputSummary = { ...input };
549
591
  for (const key2 of ["image_url", "start_image_url", "end_image_url"]) {
550
592
  if (typeof inputSummary[key2] === "string" && inputSummary[key2].startsWith("data:")) {