Files
freepik/freepik_cli/commands/generate.py
T
valknar 0de3f7d6bc fix: correct per-model video API field names and remove non-existent models
Each video model uses a different image input field:
- kling-o1-pro/std: first_frame (not image)
- kling-elements-pro/std: images (array)
- minimax-hailuo: image, duration fixed at "6"

Also:
- kling-elements requires slug aspect ratios (square_1_1, etc.)
- Remove wan-2.5 and runway-gen4 which return 404

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 18:15:59 +02:00

360 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""generate-image, generate-video, generate-icon commands."""
from __future__ import annotations
from pathlib import Path
from typing import Annotated, Optional
import typer
from freepik_cli.api.client import FreepikAPIError, FreepikClient
from freepik_cli.api.edit import EditAPI
from freepik_cli.api.images import ImageAPI
from freepik_cli.api.models import IconStyle, ImageModel, VideoModel, normalize_aspect_ratio
from freepik_cli.api.videos import VideoAPI
from freepik_cli.utils.config import FreepikConfig
from freepik_cli.utils.console import GenerationResult, console, print_error, print_no_wait, print_result
from freepik_cli.utils.files import auto_output_path, get_image_dimensions, image_to_base64, save_from_url
from freepik_cli.utils.polling import FreepikTaskError, FreepikTimeoutError, PollConfig, poll_task
def _get_api_key(api_key: Optional[str], config: FreepikConfig) -> str:
key = api_key or config.api_key
if not key:
print_error(
"No API key found.",
hint="Set the [cyan]FREEPIK_API_KEY[/cyan] environment variable, "
"or pass [cyan]--api-key YOUR_KEY[/cyan].",
)
raise typer.Exit(1)
return key
def generate_image(
prompt: Annotated[str, typer.Argument(help="Text prompt describing the image to generate")],
model: Annotated[
ImageModel,
typer.Option("--model", "-m", help="AI model to use for generation", show_default=True),
] = ImageModel.FLUX_2_PRO,
output: Annotated[
Optional[Path],
typer.Option("--output", "-o", help="Output file path (auto-generated if omitted)"),
] = None,
aspect_ratio: Annotated[
Optional[str],
typer.Option(
"--aspect-ratio", "-a",
help="Aspect ratio e.g. [cyan]16:9[/cyan], [cyan]1:1[/cyan], [cyan]9:16[/cyan], [cyan]4:3[/cyan]",
),
] = None,
negative_prompt: Annotated[
Optional[str],
typer.Option("--negative-prompt", "-n", help="Concepts to exclude from the image"),
] = None,
seed: Annotated[
Optional[int],
typer.Option("--seed", help="Random seed for reproducibility"),
] = None,
input_image: Annotated[
Optional[Path],
typer.Option(
"--input-image", "-i",
help="Reference image for img2img / editing (flux-kontext-pro)",
exists=True, file_okay=True, dir_okay=False,
),
] = None,
wait: Annotated[
bool,
typer.Option("--wait/--no-wait", help="Wait for completion or return task ID immediately"),
] = True,
api_key: Annotated[
Optional[str],
typer.Option("--api-key", envvar="FREEPIK_API_KEY", help="Freepik API key"),
] = None,
) -> None:
"""
[bold]Generate an image[/bold] using Freepik AI models.
[dim]Examples:[/dim]
freepik generate-image "a cat on the moon" --model flux-2-pro
freepik generate-image "cyberpunk city at night" --model mystic --aspect-ratio 16:9
freepik generate-image "make the sky orange" --model flux-kontext-pro --input-image photo.jpg
"""
config = FreepikConfig.load()
key = _get_api_key(api_key, config)
# Build request payload
payload: dict = {"prompt": prompt}
if aspect_ratio:
payload["aspect_ratio"] = normalize_aspect_ratio(aspect_ratio, model)
if negative_prompt:
payload["negative_prompt"] = negative_prompt
if seed is not None:
payload["seed"] = seed
if input_image:
payload["image"] = image_to_base64(input_image)
with FreepikClient(key, base_url=config.base_url) as client:
api = ImageAPI(client)
with console.status(f"[info]Submitting {model.value} generation…[/info]"):
try:
task_id = api.generate(model, payload)
except FreepikAPIError as exc:
print_error(str(exc))
raise typer.Exit(1)
if not wait:
print_no_wait(task_id, "image", model.value)
return
poll_config = PollConfig(task_type="image", max_wait=config.poll_timeout)
try:
result = poll_task(
check_fn=lambda tid: api.get_status(model, tid),
task_id=task_id,
config=poll_config,
console=console,
extra_info={"Model": f"[magenta]{model.value}[/magenta]"},
)
except (FreepikTaskError, FreepikTimeoutError) as exc:
print_error(str(exc))
raise typer.Exit(1)
urls = api.get_output_urls(result)
if not urls:
print_error("Generation completed but no output URLs found.", hint="Check the Freepik dashboard.")
raise typer.Exit(1)
out = output or auto_output_path("image", model.value, "jpg", config.default_output_dir)
save_from_url(urls[0], out, console)
w, h = get_image_dimensions(out)
print_result(
GenerationResult(
task_id=task_id,
model=model.value,
output_path=out,
width=w,
height=h,
seed=seed,
task_type="image",
)
)
def generate_video(
image: Annotated[
Path,
typer.Argument(
help="Source image path to animate",
exists=True, file_okay=True, dir_okay=False,
),
],
model: Annotated[
VideoModel,
typer.Option("--model", "-m", help="Video AI model", show_default=True),
] = VideoModel.KLING_O1_PRO,
prompt: Annotated[
Optional[str],
typer.Option("--prompt", "-p", help="Motion/style guidance prompt"),
] = None,
duration: Annotated[
int,
typer.Option(
"--duration", "-d",
help="Duration in seconds: [cyan]5[/cyan] or [cyan]10[/cyan] (minimax-hailuo is fixed at 6s)",
min=5, max=10,
),
] = 5,
aspect_ratio: Annotated[
str,
typer.Option("--aspect-ratio", "-a", help="Output aspect ratio: [cyan]16:9[/cyan] | [cyan]9:16[/cyan] | [cyan]1:1[/cyan]"),
] = "16:9",
seed: Annotated[
Optional[int],
typer.Option("--seed", help="Random seed for reproducibility"),
] = None,
output: Annotated[
Optional[Path],
typer.Option("--output", "-o", help="Output video file path"),
] = None,
wait: Annotated[
bool,
typer.Option("--wait/--no-wait", help="Wait for completion or return task ID immediately"),
] = True,
api_key: Annotated[
Optional[str],
typer.Option("--api-key", envvar="FREEPIK_API_KEY", help="Freepik API key"),
] = None,
) -> None:
"""
[bold]Generate a video[/bold] from a source image using AI.
[dim]Examples:[/dim]
freepik generate-video photo.jpg --prompt "gentle ocean waves" --model kling-o1-pro
freepik generate-video portrait.png --model kling-elements-pro --aspect-ratio 9:16
freepik generate-video photo.jpg --model minimax-hailuo --aspect-ratio 16:9
"""
config = FreepikConfig.load()
key = _get_api_key(api_key, config)
image_b64 = image_to_base64(image)
with FreepikClient(key, base_url=config.base_url) as client:
api = VideoAPI(client)
with console.status(f"[info]Submitting {model.value} video generation…[/info]"):
try:
task_id = api.generate(
model=model,
image_b64=image_b64,
prompt=prompt,
duration=duration,
aspect_ratio=aspect_ratio,
seed=seed,
)
except FreepikAPIError as exc:
print_error(str(exc))
raise typer.Exit(1)
if not wait:
print_no_wait(task_id, "video", model.value)
return
poll_config = PollConfig(
task_type="video",
initial_delay=5.0,
max_wait=config.poll_timeout,
)
try:
result = poll_task(
check_fn=lambda tid: api.get_status(model, tid),
task_id=task_id,
config=poll_config,
console=console,
extra_info={
"Model": f"[magenta]{model.value}[/magenta]",
"Duration": f"{duration}s",
"Ratio": aspect_ratio,
},
)
except (FreepikTaskError, FreepikTimeoutError) as exc:
print_error(str(exc))
raise typer.Exit(1)
urls = api.get_output_urls(result)
if not urls:
print_error("Generation completed but no output URLs found.")
raise typer.Exit(1)
out = output or auto_output_path("video", model.value, "mp4", config.default_output_dir)
save_from_url(urls[0], out, console)
print_result(
GenerationResult(
task_id=task_id,
model=model.value,
output_path=out,
duration=str(duration),
task_type="video",
)
)
def generate_icon(
prompt: Annotated[str, typer.Argument(help="Text prompt for the icon")],
style: Annotated[
IconStyle,
typer.Option("--style", "-s", help="Icon style", show_default=True),
] = IconStyle.COLOR,
steps: Annotated[
int,
typer.Option("--steps", help="Inference steps (1050)", min=10, max=50),
] = 30,
guidance: Annotated[
float,
typer.Option("--guidance", help="Guidance scale (010)", min=0.0, max=10.0),
] = 7.5,
seed: Annotated[
Optional[int],
typer.Option("--seed", help="Random seed"),
] = None,
fmt: Annotated[
str,
typer.Option("--format", "-f", help="Output format: [cyan]png[/cyan] | [cyan]svg[/cyan]"),
] = "png",
output: Annotated[
Optional[Path],
typer.Option("--output", "-o", help="Output file path"),
] = None,
wait: Annotated[
bool,
typer.Option("--wait/--no-wait"),
] = True,
api_key: Annotated[
Optional[str],
typer.Option("--api-key", envvar="FREEPIK_API_KEY"),
] = None,
) -> None:
"""
[bold]Generate an icon[/bold] from a text prompt.
[dim]Examples:[/dim]
freepik generate-icon "shopping cart" --style solid --format svg
freepik generate-icon "rocket ship" --style color --format png
"""
config = FreepikConfig.load()
key = _get_api_key(api_key, config)
with FreepikClient(key, base_url=config.base_url) as client:
api = EditAPI(client)
with console.status("[info]Submitting icon generation…[/info]"):
try:
task_id = api.generate_icon(prompt, style, steps, guidance, seed)
except FreepikAPIError as exc:
print_error(str(exc))
raise typer.Exit(1)
if not wait:
print_no_wait(task_id, "icon", f"text-to-icon/{style.value}")
return
poll_config = PollConfig(task_type="icon", max_wait=config.poll_timeout)
try:
poll_task(
check_fn=lambda tid: api.icon_status(tid),
task_id=task_id,
config=poll_config,
console=console,
extra_info={"Style": style.value, "Format": fmt},
)
except (FreepikTaskError, FreepikTimeoutError) as exc:
print_error(str(exc))
raise typer.Exit(1)
# Render to get download URL
with console.status(f"[info]Rendering icon as {fmt.upper()}…[/info]"):
try:
url = api.render_icon(task_id, fmt)
except FreepikAPIError as exc:
print_error(str(exc))
raise typer.Exit(1)
if not url:
print_error("Icon generated but render URL not found.")
raise typer.Exit(1)
out = output or auto_output_path("icon", style.value, fmt, config.default_output_dir)
save_from_url(url, out, console)
print_result(
GenerationResult(
task_id=task_id,
model=f"text-to-icon/{style.value}",
output_path=out,
task_type="icon",
)
)