f24d138ab4
Sophisticated Python CLI for generating and manipulating images and video via the Freepik API, built with typer + rich. Commands: - generate-image: text-to-image with 8 models (flux-2-pro, mystic, seedream, etc.) - generate-video: image-to-video with 7 models (kling, minimax, runway, etc.) - generate-icon: text-to-icon in solid/outline/color/flat/sticker styles - upscale-image: 3 modes (precision-v2, precision, creative) + 2x/4x scale - upscale-video: standard/turbo modes - expand-image: outpainting with per-side pixel offsets - relight: AI-controlled relighting (Premium) - style-transfer: artistic style application (Premium) - describe-image: reverse-engineer an image into a prompt - config set/get/show/reset: configuration management Features: Rich Live polling panel, exponential backoff, --wait/--no-wait, auto-timestamped output filenames, streaming download with progress bar, FREEPIK_API_KEY env var support, venv-based setup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
244 lines
7.9 KiB
Python
244 lines
7.9 KiB
Python
"""upscale-image and upscale-video 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.models import UpscaleMode, VideoUpscaleMode
|
||
from freepik_cli.api.upscale import UpscaleAPI
|
||
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, video_to_base64
|
||
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 [cyan]FREEPIK_API_KEY[/cyan] or pass [cyan]--api-key YOUR_KEY[/cyan].",
|
||
)
|
||
raise typer.Exit(1)
|
||
return key
|
||
|
||
|
||
def upscale_image(
|
||
image: Annotated[
|
||
Path,
|
||
typer.Argument(
|
||
help="Image file to upscale",
|
||
exists=True, file_okay=True, dir_okay=False,
|
||
),
|
||
],
|
||
mode: Annotated[
|
||
UpscaleMode,
|
||
typer.Option(
|
||
"--mode",
|
||
help="Upscaling mode:\n"
|
||
" [cyan]precision-v2[/cyan] — faithful, detail-preserving (recommended)\n"
|
||
" [cyan]precision[/cyan] — faithful upscaling\n"
|
||
" [cyan]creative[/cyan] — AI-enhanced creative reinterpretation",
|
||
show_default=True,
|
||
),
|
||
] = UpscaleMode.PRECISION_V2,
|
||
scale: Annotated[
|
||
str,
|
||
typer.Option("--scale", help="Scale factor: [cyan]2x[/cyan] or [cyan]4x[/cyan]"),
|
||
] = "2x",
|
||
creativity: Annotated[
|
||
Optional[int],
|
||
typer.Option(
|
||
"--creativity",
|
||
help="Creative enhancement level 0–10 ([cyan]creative[/cyan] mode only)",
|
||
min=0, max=10,
|
||
),
|
||
] = None,
|
||
prompt: Annotated[
|
||
Optional[str],
|
||
typer.Option("--prompt", "-p", help="Enhancement guidance ([cyan]creative[/cyan] mode only)"),
|
||
] = None,
|
||
seed: Annotated[
|
||
Optional[int],
|
||
typer.Option("--seed", help="Random seed for reproducibility"),
|
||
] = None,
|
||
output: Annotated[
|
||
Optional[Path],
|
||
typer.Option("--output", "-o", help="Output 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]Upscale and enhance an image[/bold] using AI.
|
||
|
||
[dim]Examples:[/dim]
|
||
freepik upscale-image photo.jpg --mode precision-v2 --scale 4x
|
||
freepik upscale-image portrait.png --mode creative --creativity 7 --prompt "sharp cinematic"
|
||
"""
|
||
config = FreepikConfig.load()
|
||
key = _get_api_key(api_key, config)
|
||
|
||
if mode != UpscaleMode.CREATIVE and (creativity is not None or prompt):
|
||
print_error(
|
||
"--creativity and --prompt are only supported with --mode creative.",
|
||
hint="Switch to [cyan]--mode creative[/cyan] or remove those options.",
|
||
)
|
||
raise typer.Exit(1)
|
||
|
||
image_b64 = image_to_base64(image)
|
||
|
||
with FreepikClient(key, base_url=config.base_url) as client:
|
||
api = UpscaleAPI(client)
|
||
|
||
with console.status(f"[info]Submitting {mode.value} upscale ({scale})…[/info]"):
|
||
try:
|
||
task_id = api.upscale_image(
|
||
mode=mode,
|
||
image_b64=image_b64,
|
||
scale_factor=scale,
|
||
creativity=creativity,
|
||
prompt=prompt,
|
||
seed=seed,
|
||
)
|
||
except FreepikAPIError as exc:
|
||
print_error(str(exc))
|
||
raise typer.Exit(1)
|
||
|
||
if not wait:
|
||
print_no_wait(task_id, "upscale-image", mode.value)
|
||
return
|
||
|
||
poll_config = PollConfig(task_type="upscale-image", max_wait=config.poll_timeout)
|
||
try:
|
||
result = poll_task(
|
||
check_fn=lambda tid: api.upscale_image_status(mode, tid),
|
||
task_id=task_id,
|
||
config=poll_config,
|
||
console=console,
|
||
extra_info={"Mode": mode.value, "Scale": scale},
|
||
)
|
||
except (FreepikTaskError, FreepikTimeoutError) as exc:
|
||
print_error(str(exc))
|
||
raise typer.Exit(1)
|
||
|
||
urls = api.get_output_urls(result)
|
||
if not urls:
|
||
print_error("Upscaling completed but no output URL found.")
|
||
raise typer.Exit(1)
|
||
|
||
out = output or auto_output_path("upscaled", mode.value, image.suffix.lstrip(".") or "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=mode.value,
|
||
output_path=out,
|
||
width=w,
|
||
height=h,
|
||
task_type="upscale-image",
|
||
)
|
||
)
|
||
|
||
|
||
def upscale_video(
|
||
video: Annotated[
|
||
Path,
|
||
typer.Argument(
|
||
help="Video file to upscale",
|
||
exists=True, file_okay=True, dir_okay=False,
|
||
),
|
||
],
|
||
mode: Annotated[
|
||
VideoUpscaleMode,
|
||
typer.Option(
|
||
"--mode",
|
||
help="Upscaling mode: [cyan]standard[/cyan] | [cyan]turbo[/cyan] (faster)",
|
||
show_default=True,
|
||
),
|
||
] = VideoUpscaleMode.STANDARD,
|
||
output: Annotated[
|
||
Optional[Path],
|
||
typer.Option("--output", "-o", help="Output video 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]Upscale a video[/bold] to higher resolution using AI.
|
||
|
||
[dim]Examples:[/dim]
|
||
freepik upscale-video clip.mp4 --mode standard
|
||
freepik upscale-video clip.mp4 --mode turbo --output clip_4k.mp4
|
||
"""
|
||
config = FreepikConfig.load()
|
||
key = _get_api_key(api_key, config)
|
||
|
||
video_b64 = video_to_base64(video)
|
||
|
||
with FreepikClient(key, base_url=config.base_url) as client:
|
||
api = UpscaleAPI(client)
|
||
|
||
with console.status(f"[info]Submitting video upscale ({mode.value})…[/info]"):
|
||
try:
|
||
task_id = api.upscale_video(mode=mode, video_b64=video_b64)
|
||
except FreepikAPIError as exc:
|
||
print_error(str(exc))
|
||
raise typer.Exit(1)
|
||
|
||
if not wait:
|
||
print_no_wait(task_id, "upscale-video", mode.value)
|
||
return
|
||
|
||
poll_config = PollConfig(
|
||
task_type="upscale-video",
|
||
initial_delay=5.0,
|
||
max_wait=config.poll_timeout,
|
||
)
|
||
try:
|
||
result = poll_task(
|
||
check_fn=lambda tid: api.upscale_video_status(tid),
|
||
task_id=task_id,
|
||
config=poll_config,
|
||
console=console,
|
||
extra_info={"Mode": mode.value},
|
||
)
|
||
except (FreepikTaskError, FreepikTimeoutError) as exc:
|
||
print_error(str(exc))
|
||
raise typer.Exit(1)
|
||
|
||
urls = api.get_output_urls(result)
|
||
if not urls:
|
||
print_error("Upscaling completed but no output URL found.")
|
||
raise typer.Exit(1)
|
||
|
||
out = output or auto_output_path("upscaled_video", mode.value, "mp4", config.default_output_dir)
|
||
save_from_url(urls[0], out, console)
|
||
|
||
print_result(
|
||
GenerationResult(
|
||
task_id=task_id,
|
||
model=mode.value,
|
||
output_path=out,
|
||
task_type="upscale-video",
|
||
)
|
||
)
|