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
|
2026-04-10 18:06:40 +02:00
|
|
|
|
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:
|
2026-04-10 18:06:40 +02:00
|
|
|
|
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,
|
2026-04-10 18:15:59 +02:00
|
|
|
|
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
|
2026-04-10 18:15:59 +02:00
|
|
|
|
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 (10–50)", min=10, max=50),
|
|
|
|
|
|
] = 30,
|
|
|
|
|
|
guidance: Annotated[
|
|
|
|
|
|
float,
|
|
|
|
|
|
typer.Option("--guidance", help="Guidance scale (0–10)", 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",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|