HiAPI
Blog

Tutorial

Veo 3.1 Lite Bild-zu-Video auf HiAPI verwenden: curl- und Python-Tutorial

Nov 3, 2026
午夜蓝底上铜色精细平行刻线由密到疏流动,抽象呼应图像转为视频的教程封面图。

Was veo-3.1-lite/image-to-video leistet

veo-3.1-lite/image-to-video ist ein Modell, das ein erstes Bild in ein Video umwandelt. Du gibst ihm ein statisches Bild, und es animiert dieses Bild und erzeugt daraus ein kurzes Video. Das von dir bereitgestellte Bild ist keine Stilreferenz und keine vage Inspirationsquelle – es ist buchstäblich das erste Bild des Ausgabevideos, sodass Komposition, Position des Motivs und Bildausschnitt direkt in die ersten Sekunden der Bewegung übergehen.

Diese Designentscheidung bestimmt, wie du die Eingabe betrachten solltest. Ein gut beleuchtetes, scharfes und gut zugeschnittenes Bild erzeugt ein Video, das an der von dir erwarteten Stelle beginnt. Ein unscharfes oder seltsam zugeschnittenes Bild erzeugt ein Video, das ebenfalls unscharf und seltsam zugeschnitten beginnt und beim Generieren der Bewegung weiter driftet. Der größte Teil der Arbeit, dieses Modell gut zu nutzen, findet statt, bevor du eine Zeile Code schreibst.

Auf der Ausgabeseite liefert das Modell ein Video mit nativem Audio. Genau darin unterscheidet es sich von älteren Bild-zu-Video-Pipelines – ältere Abläufe erzeugen zuerst stumme Bilder und fügen dann separat eine unabhängige Tonspur hinzu. Hier wird das Audio als Teil derselben Aufgabe generiert, sodass Umgebungsgeräusche und visuelle Szene kohärent bleiben – kein manuelles Mischen erforderlich, und es gibt keine zeitliche Verschiebung zwischen Soundeffekten und der zugehörigen Bewegung.

Die Generierung ist asynchron. Du reichst eine Aufgabe ein, erhältst einen Bezeichner und fragst dann ab, bis die Aufgabe abgeschlossen ist. Das ist eine bewusste Architekturentscheidung: Die Videogenerierung dauert weit länger als typische HTTP-Request-Timeouts, daher entkoppelt die API Einreichung und Abschluss. Jedes Beispiel in diesem Tutorial folgt diesem Muster „zuerst einreichen, dann abfragen“.

Kurz gesagt: ein Bild hinein, ein Video mit Audio hinaus, geliefert über eine asynchrone Aufgabe. Behalte dieses mentale Modell im Kopf, und der Rest der API ist unkompliziert.

Bevor du die erste Anfrage sendest

Für einen erfolgreichen ersten Aufruf müssen drei Dinge vorbereitet sein.

Ein API-Schlüssel

Jede Anfrage an HiAPI.ai wird mit einem API-Schlüssel authentifiziert. Erstelle einen im Konto-Dashboard und lade ihn aus einer Umgebungsvariable, statt ihn direkt in die Quelldatei einzufügen:

export HIAPI_API_KEY="your_api_key_here"

Falls ein Schlüssel jemals in ein Repository eingecheckt wurde, rotiere ihn sofort. Ein Schlüssel ist billig zu ersetzen, aber teuer, wenn er preisgegeben wird.

Ein erreichbares erstes Bild

Das Modell muss das Bild selbst abrufen, daher muss das Bild über eine URL erreichbar sein – ein öffentlicher Objektspeicher-Link, ein CDN-Pfad oder ein beliebiger HTTP-Endpunkt, der keine interaktive Authentifizierung erfordert. Signierte URLs mit langer Gültigkeit sind in Ordnung; URLs, die eine Anmeldesitzung erfordern, sind es nicht. Verwende gängige Formate wie PNG oder JPEG und wähle eine sinnvolle Auflösung: Ein scharfes, mittelgroßes Bild liefert dem Modell saubere Details, ohne Bandbreite zu verschwenden.

Wenn dein Quellbild hinter einem internen Dienst liegt, lade es zuerst in den Objektspeicher hoch und übergib dann die erzeugte URL. Das ist oft die häufigste Ursache für einen fehlgeschlagenen ersten Versuch.

Der Endpunktpfad

Das Modell wird durch veo-3.1-lite/image-to-video identifiziert. Behandle diesen String als Modellbezeichner, der mit der Aufgabe gesendet wird, und nicht als hartkodiert an einem Dutzend Stellen – lege ihn in eine Konstante, denn du wirst später wahrscheinlich zwischen verschiedenen Modellvarianten wechseln.

Sobald du den Schlüssel exportiert, eine abrufbare Bild-URL und den Modellbezeichner hast, bist du bereit einzureichen.

Eine funktionierende curl-Anfrage

Der Einreichungsaufruf erstellt eine Generierungsaufgabe. Ein vollständiges, lauffähiges Beispiel sieht so aus:

curl -X POST "https://api.hiapi.ai/v1/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "veo-3.1-lite/image-to-video",
    "image": "https://your-bucket.example.com/first-frame.jpg",
    "prompt": "Morgenlicht bewegt sich langsam durch die Szene, die Kamera fährt langsam vorwärts",
    "duration": 5
  }'

Punkt für Punkt:

  • Die URL ist der Generierungsendpunkt auf HiAPI. Der gesamte Modellverkehr läuft über diese eine Schnittstelle, sodass ein Modellwechsel später nur das Feld model ändert, nicht den Endpunkt.
  • Der Authorization-Header trägt dein Bearer-Token. Die Schreibweise $HIAPI_API_KEY liest aus der Umgebungsvariable, sodass der Schlüssel niemals in der Shell-Historie auftaucht.
  • model identifiziert veo-3.1-lite/image-to-video.
  • image ist die URL des ersten Bildes. Genau dieses Feld macht den Aufruf zu Bild-zu-Video statt Text-zu-Video.
  • prompt ist optional, aber dringend empfohlen. Er beschreibt die gewünschte Bewegung – Kamerabewegung, Lichtveränderungen, Bewegungen des Motivs – und nicht die Beschreibung des statischen Bildes, denn das Modell kann dieses Bild bereits sehen.
  • duration ist die angeforderte Videodauer in Sekunden, begrenzt durch den vom Modell unterstützten Bereich.

Die Antwort ist ein Aufgabenobjekt mit einer id. Speichere sie:

{
  "id": "job_abc123",
  "status": "queued",
  "model": "veo-3.1-lite/image-to-video"
}

Ein Status von queued oder processing ist das erwartete Ergebnis – du hast noch kein Video, sondern nur ein Versprechen. Der nächste Abschnitt zeigt, wie man aus dem Versprechen eine Datei macht.

Derselbe Aufruf in Python

Der curl-Aufruf lässt sich direkt mit requests nach Python übertragen. Die folgende Version kapselt die Einreichung in eine kleine Funktion, die sich in Pipelines wiederverwenden lässt:

import os
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

def submit_video_job(image_url: str, prompt: str, duration: int = 5) -> str:
    payload = {
        "model": "veo-3.1-lite/image-to-video",
        "image": image_url,
        "prompt": prompt,
        "duration": duration,
    }
    response = requests.post(
        f"{BASE_URL}/video/generations",
        headers=HEADERS,
        json=payload,
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()
    print(f"Submitted job {data['id']} ({data['status']})")
    return data["id"]

if __name__ == "__main__":
    job_id = submit_video_job(
        image_url="https://your-bucket.example.com/first-frame.jpg",
        prompt="Morgenlicht bewegt sich langsam durch die Szene, die Kamera fährt langsam vorwärts",
    )

Einige Details lohnt es sich zu behalten:

  • raise_for_status() verwandelt Nicht-2xx-Antworten frühzeitig in Ausnahmen. Ohne sie sieht ein Authentifizierungs- oder Validierungsfehler wie ein erfolgreicher Aufruf aus, der einen unerwarteten Antwortkörper zurückgibt.
  • Das 30-Sekunden-Timeout gilt nur für die Einreichung. Die Einreichung sollte schnell zurückkehren; die lange Arbeit findet serverseitig statt. Wenn die Einreichung selbst ein Timeout erreicht, liegt das Problem bei der Konnektivität oder der Größe des Anfrage-Payloads, nicht bei der Generierungsgeschwindigkeit.
  • Die Rückgabe der Aufgaben-ID statt der gesamten Antwort hält die Funktion komponierbar – der Aufrufer entscheidet, wie mit der Aufgabe umgegangen wird.

Damit lässt sich die Funktion unverändert in einen Queue-Worker, einen Flask-Endpunkt oder eine Notebook-Zelle einfügen.

Asynchrone Aufgaben abfragen

Die Videogenerierung dauert Minuten, nicht Millisekunden. Frage den Status-Endpunkt der Aufgabe in Abständen ab, die ungefähr dem menschlichen Rhythmus entsprechen, und höre sofort auf, wenn die Aufgabe einen Endzustand erreicht:

import time
import requests

def wait_for_video(job_id: str, timeout: int = 600, interval: int = 10) -> dict:
    deadline = time.time() + timeout
    url = f"{BASE_URL}/video/generations/{job_id}"
    while time.time() < deadline:
        response = requests.get(url, headers=HEADERS, timeout=30)
        response.raise_for_status()
        data = response.json()
        status = data["status"]
        print(f"Job {job_id}: {status}")
        if status == "completed":
            return data
        if status == "failed":
            raise RuntimeError(f"Generation failed: {data}")
        time.sleep(interval)
    raise TimeoutError(f"Job {job_id} did not finish within {timeout}s")

Drei Praktiken machen dies im Produktivbetrieb zuverlässig:

  1. Exponentielles Backoff. Beginne mit kurzen Abständen, erhöhe sie schrittweise und setze eine sinnvolle Obergrenze. Die erste Abfrage liefert oft noch queued, daher bringt es nichts, den Endpunkt in den ersten Sekunden zu bombardieren.
  2. Hartes Timeout. Setze immer eine Obergrenze für die gesamte Wartezeit. Aufgaben, die in processing hängen bleiben, sollten als alarmierbarer Fehler auftreten, nicht als Endlosschleife.
  3. Aufgaben-ID persistieren. Schreibe sie in die Datenbank, bevor du mit dem Abfragen beginnst. Wenn der Prozess mitten im Warten abstürzt, kann er nach einem Neustart weiter abfragen, statt erneut einzureichen und für dieselbe Arbeit doppelt zu bezahlen.

Betrachte nur completed und failed als Zustände, die die Schleife beenden. Alles andere – queued, processing – bedeutet weiter warten.

Noch ein Hinweis: Wenn du diesen Code auf einem Webserver ausführst, frage nicht innerhalb des Request-Handlers ab. Reiche ein, gib die Aufgaben-ID an den Client zurück und lasse einen Hintergrund-Worker das Warten übernehmen. Andernfalls belegt eine langsame Generierung minutenlang eine Verbindung.

Das Ausgabevideo herunterladen

Wenn die Aufgabe abgeschlossen ist, enthält die Statusantwort eine URL zum fertigen Video. Lade sie als Binärdaten herunter und schreibe sie mit passender Erweiterung auf die Festplatte:

def download_video(job: dict, path: str = "output.mp4") -> str:
    video_url = job["output"]["url"]
    response = requests.get(video_url, headers=HEADERS, timeout=120, stream=True)
    response.raise_for_status()
    with open(path, "wb") as f:
        for chunk in response.iter_content(chunk_size=1 << 20):
            f.write(chunk)
    print(f"Saved {path}")
    return path

Einige Dinge sollte man richtig machen:

  • Im Binärmodus schreiben. Eine Datei mit "w" statt "wb" zu öffnen beschädigt sie stillschweigend; der Download scheint zu gelingen, aber das Video lässt sich nicht abspielen.
  • Große Dateien streamen. Das obige stream=True plus das Kopieren in Blöcken hält den Speicher unabhängig von der Videogröße stabil.
  • Prüfsummen berechnen, wenn du Ausgaben in großem Umfang archivierst. Das ist nur eine Zeile Code, fängt aber abgeschnittene Downloads ab, bevor sie Benutzer erreichen.

Zum Audio: Das zurückgegebene Video enthält bereits seine native Tonspur. Es gibt keine separate Audiodatei abzurufen und keinen Mischschritt. Wenn du die Datei nachgelagert neu verpackst – etwa das Containerformat änderst –, nimm den Audiostream mit. Ein häufiger Fehler ist, eine Konvertierung auszuführen, die Audio standardmäßig entfernt, und sich dann zu fragen, wo das Audio geblieben ist.

Schließlich sind Ausgabe-URLs in der Regel zeitlich begrenzt. Lade das Video so bald wie möglich herunter und speichere es in deinem eigenen Bucket, statt die URL tagelang aufzubewahren und dann abzurufen; zu diesem Zeitpunkt ist der Link möglicherweise abgelaufen.

Häufige Fehlerquellen

Die folgenden Fehler kosten bei der Fehlersuche von Bild-zu-Video-Aufrufen die meiste Zeit:

Bild-URL nicht erreichbar

Dies ist mit Abstand die häufigste Fehlerursache. Das Modell ruft dein Bild serverseitig ab, daher sagt eine URL, die in deinem angemeldeten Browser funktioniert, nichts aus. Teste sie mit einer gewöhnlichen Anfrage ohne Authentifizierung, bevor du die Aufgabe einreichst. Signierte URLs sollten eine Gültigkeit haben, die deutlich länger als die Generierungsdauer ist.

Authentifizierungsfehler, die wie Modellfehler aussehen

Ein fehlender oder falsch formatierter Authorization-Header liefert normalerweise eine klare 401, aber gelegentlich packen Entwickler den Initialisierungscode in eine Logik, die Statuscodes verschluckt, und melden dann „Modellfehler“. Prüfe den rohen Statuscode, bevor du den Anfrage-Payload debuggst.

Zu häufiges Abfragen oder gar kein Abfragen

Alle 100 Millisekunden abzufragen verschwendet Kontingent und kann Rate-Limits auslösen. Nur einmal abzufragen und bei processing aufzugeben bedeutet, dass die Aufgabe erfolgreich ist, aber niemand davon erfährt. Folge dem oben beschriebenen Muster aus Backoff plus Timeout.

Die Einreichung für den Abschluss halten

Eine erfolgreiche Einreichung liefert eine Aufgabe, kein Video. Code, der das Feld url aus der Einreichungsantwort liest, findet nichts. Nur die Antwort des Abschlussstatus enthält die Ausgabe.

Prompts, die das Bild beschreiben

Ein Prompt wie „eine Katze sitzt auf einer Fensterbank“ wiederholt nur, was das Modell bereits sieht. Prompts, die Bewegung beschreiben – „die Kamera fährt langsam zurück, während es zu regnen beginnt“ –, erzeugen bessere Ergebnisse. Beschreibe Veränderung, nicht Inhalt.

Aufgaben-ID bei Absturz verloren

Wenn der Prozess zwischen Einreichung und Abschluss stirbt, bedeutet eine nicht persistierte Aufgaben-ID, dass die Arbeit nicht wiederhergestellt werden kann. Persistiere sie und frage sie asynchron aus dem Worker ab. Eine kleine Designentscheidung erspart den längsten Debugging-Prozess.

Ähnliche Artikel

Alle Artikel ansehen →

Starte mit persistenten KI-Artefakten

Hol dir deinen API-Key und rufe Bild-, Video- und Audiomodelle über eine einzige task-basierte API auf.

API-Dokumentation lesen