Files
valknar 941fd14ccf refactor: rename project from Freepik to Magnific
Rename all identifiers, strings, file names, env vars, CLI entry point,
ASCII banner, and API endpoint to reflect the company rebrand.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 13:06:16 +02:00

360 lines
12 KiB
Python
Raw Permalink 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 magnific_cli.api.client import MagnificAPIError, MagnificClient
from magnific_cli.api.edit import EditAPI
from magnific_cli.api.images import ImageAPI
from magnific_cli.api.models import IconStyle, ImageModel, VideoModel, normalize_aspect_ratio
from magnific_cli.api.videos import VideoAPI
from magnific_cli.utils.config import MagnificConfig
from magnific_cli.utils.console import GenerationResult, console, print_error, print_no_wait, print_result
from magnific_cli.utils.files import auto_output_path, get_image_dimensions, image_to_base64, save_from_url
from magnific_cli.utils.polling import MagnificTaskError, MagnificTimeoutError, PollConfig, poll_task
def _get_api_key(api_key: Optional[str], config: MagnificConfig) -> str:
key = api_key or config.api_key
if not key:
print_error(
"No API key found.",
hint="Set the [cyan]MAGNIFIC_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="MAGNIFIC_API_KEY", help="Magnific API key"),
] = None,
) -> None:
"""
[bold]Generate an image[/bold] using Magnific AI models.
[dim]Examples:[/dim]
magnific generate-image "a cat on the moon" --model flux-2-pro
magnific generate-image "cyberpunk city at night" --model mystic --aspect-ratio 16:9
magnific generate-image "make the sky orange" --model flux-kontext-pro --input-image photo.jpg
"""
config = MagnificConfig.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 MagnificClient(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 MagnificAPIError 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 (MagnificTaskError, MagnificTimeoutError) 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 Magnific 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="MAGNIFIC_API_KEY", help="Magnific API key"),
] = None,
) -> None:
"""
[bold]Generate a video[/bold] from a source image using AI.
[dim]Examples:[/dim]
magnific generate-video photo.jpg --prompt "gentle ocean waves" --model kling-o1-pro
magnific generate-video portrait.png --model kling-elements-pro --aspect-ratio 9:16
magnific generate-video photo.jpg --model minimax-hailuo --aspect-ratio 16:9
"""
config = MagnificConfig.load()
key = _get_api_key(api_key, config)
image_b64 = image_to_base64(image)
with MagnificClient(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 MagnificAPIError 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 (MagnificTaskError, MagnificTimeoutError) 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="MAGNIFIC_API_KEY"),
] = None,
) -> None:
"""
[bold]Generate an icon[/bold] from a text prompt.
[dim]Examples:[/dim]
magnific generate-icon "shopping cart" --style solid --format svg
magnific generate-icon "rocket ship" --style color --format png
"""
config = MagnificConfig.load()
key = _get_api_key(api_key, config)
with MagnificClient(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 MagnificAPIError 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 (MagnificTaskError, MagnificTimeoutError) 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 MagnificAPIError 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",
)
)