Files
freepik/freepik_cli/commands/generate.py
T

360 lines
12 KiB
Python
Raw Normal View History

2026-04-08 10:56:45 +02:00
"""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
2026-04-08 10:56:45 +02:00
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)
2026-04-08 10:56:45 +02:00
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,
),
2026-04-08 10:56:45 +02:00
] = 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
2026-04-08 10:56:45 +02:00
"""
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",
)
)