Stwórz obraz, zatwierdź go, a następnie animuj w krótki klip. Te kroki odtwarzają nasz test niebieskiego kubka z października 2026. W przewodniku używamy obrazu Z-Image za 0,010 $ oraz pięciu sekund MiniMax H3 w 768p za 0,185 $: 0,195 $ za jedną próbę na każdym etapie, z wyłączonym rozszerzaniem promptu dla obrazu. Najpierw wypróbuj tę samą scenę w Playground; gdy dane wejściowe będą działać, użyj przykładu Python lub powierz wywołania agentowi.
Cel jest konkretny: prawidłowo uformowany kubek na stole roboczym, para przesuwająca się w prawo i powolny najazd kamery zakończony zatrzymaniem. Nie musisz wdrażać modelu ani konfigurować serwera GPU.
Wypróbuj cały przykład w Playground
Utwórz konto i zasil je, a potem zweryfikuj e-mail. Playground obsługuje żądanie; klucz API jest potrzebny dopiero do kodu i narzędzi agenta poniżej.
1. Wygeneruj obraz początkowy
Otwórz Z-Image tekst na obraz i skorzystaj z Playground. Wklej ten prompt:
Photograph of a handmade blue ceramic mug of tea on a worn wooden workbench in a pottery studio, a thin curl of steam rising from the mug. Soft window light from the left, shallow depth of field, shelves of pots softly out of focus behind. The mug sits left of centre with empty bench to its right. Natural colours, no text, no logos.
Wybierz 16:9, PNG, seed 42 i wyłącz rozszerzanie promptu. Sprawdź wyświetloną cenę, a następnie naciśnij Run model. Obraz podstawowy kosztuje 0,010 $; rozszerzanie zwiększyłoby koszt do 0,016 $. Utrzymuj seed 42 bez zmian, porównując modyfikacje promptu.
Otwórz wynik przed przejściem dalej. Sprawdź:
- Obiekt: jeden kubek, jedno ucho i poprawna geometria ceramiki.
- Możliwość ruchu: widoczna para i miejsce po prawej na jej przesuwanie się.
- Kompozycja: kubek stoi na lewo od środka; półki pozostają spokojnym tłem.
- Niepożądane szczegóły: brak przypadkowych napisów, logo i dodatkowych kubków.
Pobierz zaakceptowany PNG jako image.png. Jeśli kubek jest nieprawidłowy, zmień jedno zdanie i stwórz nowy obraz, zanim wydasz pieniądze na wideo. Każdy kolejny obraz podstawowy dodaje 0,010 $.
2. Animuj zatwierdzony obraz
Otwórz MiniMax H3 obraz na wideo, dodaj image.png jako Input image i wklej w Prompt:
Steam rises from the mug and drifts to the right. The camera pushes in slowly toward the mug, then holds. The mug, bench and shelves stay where they are. Quiet room, a faint ceramic clink.
Ustaw Duration na 5 sekund i Resolution na 768p; sprawdź cenę i naciśnij Run model. Ten klip kosztuje 0,185 $; wybór 2k kosztowałby 0,290 $.
Prompt oddziela ruch pary, ruch kamery i dźwięk. Nakazuje kubkowi pozostać na miejscu zamiast ponownie go opisywać. Jeśli kubek wędruje po stole, uprość instrukcję kamery zamiast dodawać kolejne działania obiektu. Powolny najazd przybliża kamerę; nie jest prośbą o poruszenie kubka.
3. Sprawdź klip i wybierz następne ujęcie
Odtwórz cały wynik z dźwiękiem. Sprawdź, czy kubek nie przesuwa się i nie zmienia kształtu, kamera wykonuje najazd i zatrzymuje się, nie ma niechcianego cięcia, a dźwięk pasuje do spokojnej pracowni. Oczekiwany wynik to MP4 trwający około pięciu sekund ze ścieżką audio; sam fakt odtwarzania pliku nie oznacza, że ujęcie nadaje się do użycia.
Jeśli obraz był dobry, ale ruch zły, zachowaj zaakceptowany obraz początkowy i popraw prompt ruchu. Jeśli obraz początkowy był zły, najpierw go wymień. H3 nie ma seed: generowanie w 2k daje nowe ujęcie, w którym ruch może być inny niż w polubionej wersji 768p.
Co powstało w teście niebieskiego kubka
Obraz kosztował 0,010 $ i został dostarczony jako PNG 1280 × 720. Żądanie pięciosekundowego wideo kosztowało 0,125 $ (w 480p, który nie jest już oferowany; ten sam klip w 768p kosztuje 0,185 $) i zwróciło MP4 832 × 480 ze ścieżką audio. Łącznie było to 0,135 $; w 768p byłoby 0,195 $.
Kubek pozostał na stole, a para unosiła się w górę i w prawo; najazd kamery był delikatny. Obraz umieścił kubek tylko nieznacznie na lewo od środka, a pod koniec para stała się mniej wyraźna w pobliżu półki. Jeśli chcesz ciaśniejszego ujęcia produktu, doprecyzuj pozycję kubka w prompcie obrazu przed animowaniem.
Dlaczego te dwa modele pasują do pierwszej próby
Z-Image daje niedrogi obraz do ustalenia obiektu i kompozycji. H3 przyjmuje go i generuje ruch oraz ścieżkę dźwiękową. Dane wejściowe różnią się:
| Z-Image tekst na obraz | H3 obraz na wideo | |
|---|---|---|
| ID modelu | alibaba/z-image-turbo/text-to-image | minimax/h3/image-to-video |
| Wymagane dane | prompt (od 1 do 1 000 znaków) | prompt (do 7 000) i image_url |
| Użyte ustawienia | aspect_ratio (1:1, 4:3, 3:4, 16:9 lub 9:16), output_format (tylko png), seed, enable_prompt_expansion | duration_seconds (od 4 do 15), resolution (768p lub 2k) |
| Nieobsługiwane | każdy inny format wyjściowy | seed i aspect_ratio: obraz początkowy wyznacza kształt |
| Wynik | jeden PNG | jeden MP4 z wygenerowaną ścieżką dźwiękową |
| Cena | 0,010 $ za obraz | 0,037 $ za sekundę w 768p |
Użyj obrazu początkowego PNG, JPEG, WebP lub GIF, z każdym bokiem od 256 do 5 760 pikseli, proporcjami od 2:5 do 5:2 i plikiem mniejszym niż 30 MB. Przesłany PNG jest najprostszą opcją. Publiczny link musi zwracać właściwy obraz; strona udostępniania dysku w chmurze nie jest obrazem wejściowym.
Uruchom ten sam przykład za pomocą Python
Użyj Python 3.11 lub nowszego, zainstaluj oficjalny pakiet poleceniem python -m pip install spicyapi i zapewnij dostęp do narzędzia file. Ustaw SPICY_API_KEY we własnym terminalu, zapisz kod jako blue-mug.py i uruchom python blue-mug.py. Prosi o zgodę na obu płatnych etapach i zatrzymuje się na kontrolę obrazu. Zachowaj utworzony katalog uruchomienia przy wznawianiu; nowy RUN_NAME wybierz tylko przy świadomie nowej próbie. Kontrakt API i opcje SDK znajdziesz w dokumentacji SDK Python.
import hashlib
import json
import os
from pathlib import Path
import subprocess
import tempfile
import time
import urllib.request
import uuid
from spicyapi import SpicyClient, output_assets
# Change RUN_NAME for a deliberately new pair of paid generations.
folder = Path(os.getenv("RUN_NAME", "blue-mug-1"))
folder.mkdir(exist_ok=True, mode=0o700)
state_file = folder / "state.json"
state = json.loads(state_file.read_text()) if state_file.exists() else {}
identity = hashlib.sha256(os.environ["SPICY_API_KEY"].encode()).hexdigest()
if state.get("identity", identity) != identity:
raise RuntimeError("This saved run belongs to a different API key.")
state["identity"] = identity
client = SpicyClient()
def save() -> None:
temp = state_file.with_suffix(".tmp")
temp.write_text(json.dumps(state, indent=2))
temp.chmod(0o600)
temp.replace(state_file)
def run(stage: str, model: str, inputs: dict) -> dict:
request = {"model": model, "input": inputs}
if stage not in state:
state[stage] = {"request": request, "key": str(uuid.uuid4())}
saved = state[stage]
if saved["request"] != request:
raise RuntimeError("Input changed: choose a new RUN_NAME, not a new retry key.")
save() # The same request and key survive a lost create response.
if not saved.get("taskId"):
if "first_sent" in saved and time.time() - saved["first_sent"] >= 23 * 3600:
raise RuntimeError("Unknown acceptance: reconcile task history before retrying.")
if "quote" not in saved:
quote = client.quote_task(model=model, input_data=inputs)
if input(f"{stage}: quoted ${quote['estimatedCost']}. Create? [y/N] ") != "y":
raise SystemExit("No task created for this stage.")
saved["quote"] = quote
saved.setdefault("first_sent", time.time())
save()
quote = saved["quote"]
task = client.create_task(model=model, input_data=inputs,
idempotency_key=saved["key"],
quote_id=quote["quoteId"],
expected_cost=quote["estimatedCost"])
saved["taskId"] = task["taskId"]
save()
task_id = saved["taskId"]
print(stage, "task:", task_id)
deadline = time.monotonic() + 1200
while time.monotonic() < deadline:
task = client.get_task(task_id)
if task["state"] in {"failed", "canceled", "expired"}:
raise RuntimeError(f"{task_id}: {task.get('errorCode')} {task.get('errorMessage')}")
if task["state"] == "succeeded":
assets = output_assets(task)
if not assets or assets[0].get("unavailable"):
raise RuntimeError(f"{task_id}: no retrievable output; keep the task ID.")
asset = assets[0]
if not asset.get("pending") and asset.get("url"):
print("Charge:", task.get("cost"), "settled:", task.get("settled"))
return asset
time.sleep(5)
raise TimeoutError(f"Resume {task_id} by rerunning this file; do not start another task.")
def download(asset: dict, destination: Path, mime: str) -> None:
temporary = None
try:
request = urllib.request.Request(asset["url"], headers={"User-Agent": "SpicyAPI-Blog-Example/1.0"})
with urllib.request.urlopen(request, timeout=300) as response:
if response.status != 200 or response.headers.get_content_type() != mime:
raise RuntimeError("Unexpected download status or content type.")
with tempfile.NamedTemporaryFile(dir=folder, delete=False) as handle:
temporary = Path(handle.name)
while chunk := response.read(1024 * 1024):
handle.write(chunk)
size = temporary.stat().st_size
expected = int(asset.get("bytes") or 0)
detected = subprocess.check_output(["file", "-b", "--mime-type", str(temporary)], text=True).strip()
if not size or (expected and size != expected) or detected != mime:
raise RuntimeError("Incomplete or incorrectly formatted download.")
temporary.replace(destination) # An interrupted transfer leaves the old file alone.
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)
image_input = {
"prompt": (
'Photograph of a handmade blue ceramic mug of tea on a worn wooden workbench '
'in a pottery studio, a thin curl of steam rising from the mug. Soft window '
'light from the left, shallow depth of field, shelves of pots softly out of '
'focus behind. The mug sits left of centre with empty bench to its right. '
'Natural colours, no text, no logos.'
),
"aspect_ratio": "16:9",
"output_format": "png",
"seed": 42,
"enable_prompt_expansion": False,
}
image_asset = run("image", "alibaba/z-image-turbo/text-to-image", image_input)
download(image_asset, folder / "image.png", "image/png")
if input(f"Open {folder / 'image.png'}. Is the mug/frame approved? [y/N] ") != "y":
raise SystemExit("Stopped before video generation.")
if not state.get("image_uri"):
state["image_uri"] = client.upload_file(str(folder / "image.png"))["uri"]
save()
video_input = {"image_url": state["image_uri"], "prompt": 'Steam rises from the mug and drifts to the right. The camera pushes in slowly toward the mug, then holds. The mug, bench and shelves stay where they are. Quiet room, a faint ceramic clink.',
"duration_seconds": 5, "resolution": "768p"}
video_asset = run("video", "minimax/h3/image-to-video", video_input)
download(video_asset, folder / "video.mp4", "video/mp4")
print("Play", folder / "video.mp4", "with sound and review it before using it.")Trzymaj klucz API na swoim serwerze lub kontrolowanym komputerze, nigdy na publicznej stronie ani w aplikacji mobilnej. Przed integracją innego modelu sprawdzaj aktualną dostępność i obsługiwane dane na stronach modeli powyżej.
Powierz wywołania agentowi, gdy to wygodniejsze
Skorzystaj z agenta, jeśli chcesz, by przygotował dane i zwrócił wynik, podczas gdy Ty zatwierdzasz cenę i decyzje kreatywne. Osobiście sprawdź obraz początkowy przed autoryzacją wideo.
- CLI: zapisz ten sam obiekt wejściowy jako
input.jsoni użyjnpx @spicyapi/cli tasks create --model alibaba/z-image-turbo/text-to-image --input-file input.json --wait. CLI potwierdza cenę; zachowaj zwrócone ID zadania do wznowienia. Dla klipu prześlij zatwierdzony obraz poleceniemnpx @spicyapi/cli files upload image.png, potem utwórzminimax/h3/image-to-videotak samo, używając zwróconego URIspicy://jakoimage_url. - MCP: poproś asystenta o
spicyapi_task_createdlaalibaba/z-image-turbo/text-to-image, potemspicyapi_task_wait; dla klipu dodajspicyapi_upload_filedla zatwierdzonegoimage.png, potemspicyapi_task_createzminimax/h3/image-to-video, 5 sekundami, 768p i powyższym promptem ruchu. Serwer MCP prosi o potwierdzenie ceny przed utworzeniem. - Skill: zainstaluj przez
npx skills add spicyapi-ai/spicy-skill, następnie poproś$spicyapio ten sam przebieg i zatrzymanie się na Twoją zgodę. Skill korzysta ze skonfigurowanego połączenia CLI lub MCP.
Zaplanuj budżet na próby, nie tylko końcowy klip
To szacunki w dolarach amerykańskich. Potwierdź aktualną cenę przed generowaniem.
| Żądanie | Szacowana cena |
|---|---|
| Jeden obraz Z-Image, PNG 16:9 | 0,010 $ |
| Ten sam obraz z rozszerzaniem promptu | 0,016 $ |
| H3, 4 s w 768p | 0,148 $ |
H3, 5 s w 768p (domyślnie, jeśli pominięto resolution) | 0,185 $ |
| H3, 5 s w 2k | 0,290 $ |
| H3, 10 s w 768p | 0,370 $ |
| H3, 15 s w 2k | 0,870 $ |
- Długość i rozdzielczość: H3 kosztuje 0,037 $ lub 0,058 $ za sekundę w 768p lub 2k.
- Nieobsługiwane kombinacje: klip 4-sekundowy w 2k jest odrzucany błędem
40004. - Dwie wersje robocze: jeden obraz i dwa pięciosekundowe wideo 768p kosztują 0,380 $.
- Wersja robocza i końcowe ujęcie: jeden obraz, pięciosekundowy test 768p i jedno pięciosekundowe ujęcie 2k kosztują 0,010 $ + 0,185 $ + 0,290 $ = 0,485 $, przy ponownym użyciu zatwierdzonego obrazu.
Pieniądze są rezerwowane po przyjęciu zadania i rozliczane po jego zakończeniu. Zadania nieudane lub wygasłe zwykle zwalniają rezerwację; niektóre odmowy dotyczące treści są płatne zgodnie z podaną polityką modelu. Przy sprawdzaniu opłaty sprawdź rzeczywisty cost i stan settled zadania.
Napraw problem na właściwym etapie
| Objaw | Co zmienić |
|---|---|
| Kubek ma dwa ucha lub przypadkowy tekst | Popraw prompt obrazu i zatwierdź nowy obraz początkowy przed generowaniem wideo |
| Kubek rusza się, choć miała ruszać się kamera | Zachowaj obraz; poproś o jeden ruch kamery i wyraźnie unieruchom kubek |
| Wideo wygląda dobrze w 768p, ale nowe ujęcie 2k jest inne | Traktuj je jako nowe ujęcie i sprawdź ruch przed akceptacją |
| Link obrazu nie działa | Prześlij plik lub użyj bezpośredniego, dostępnego URL obrazu zamiast strony udostępniania |
Konto zwraca 401 lub 40310 | Sprawdź klucz lub dokończ weryfikację e-mail |
40301 / 40302 / 40303 | Sprawdź uprawnienia modelu, listę dozwolonych IP klucza lub dostępność regionalną |
40201 / 40202 | Sprawdź saldo lub limit wydatków konta/klucza; zachowaj zapisane żądanie podczas uzgadniania nieudanego wysłania |
Żądanie odrzuca seed, aspect_ratio lub output_format | Sprawdź dane obsługiwane przez wybrane zadanie; nie kopiuj pól Z-Image do H3 |
40004 | Zmień niezgodną kombinację: 4 sekundy w 2k |
40901 lub konflikt idempotencji | Uzyskaj nową wycenę: jeśli nie zapisano ID zadania, usuń wpis tej fazy w state.json i uruchom ponownie. Jeśli poprzednia próba mogła zostać przyjęta, sprawdź listę zadań przed utworzeniem czegokolwiek; zobacz opis błędów |
429 lub błąd połączenia | Zachowaj katalog uruchomienia i ID zadania; nie zaczynaj kolejnej płatnej próby tylko po to, by odzyskać wynik |
Zadanie zgłasza content_rejected lub timeout | Przeczytaj błąd i opłatę, potem świadomie wybierz zmieniony prompt, inny obraz źródłowy lub krótsze ujęcie |
| Generowanie powiodło się, ale wynik nie jest gotowy | Wznów to samo zapisane zadanie; nie generuj kolejnego klipu, aby uzyskać pobieranie |
Ogranicz pierwszy proces do minimum
Gdy ujęcie działa, wybierz długość i rozdzielczość do dostarczenia, następnie wygeneruj i sprawdź tę wersję. Opcjonalny obraz końcowy może ukierunkować zakończenie przez last_image_url H3; nie gwarantuje dokładnej ostatniej klatki ani ruchu między klatkami.
W aplikacji zachowaj oba ID zadań i przechowuj zaakceptowane pliki we własnym magazynie. Porównanie odpytywania i Webhooks pomoże wybrać odbiór wyników, przechowywanie mediów zachować pliki, a generowanie wsadowe przygotować wiele ujęć. Pierwszy cel pozostaje ten sam: zatwierdzony obraz i klip, który rzeczywiście realizuje zamierzone ujęcie.







