Zum Hauptinhalt springen

Bild- und Videoauslieferung in eine KI-Begleiter-App einbauen

Erzeuge freigegebene Begleiterbilder und Kurzclips: Szene festhalten, Kosten abfragen, Async-Jobs wiederherstellen, geprüfte Assets ohne Dubletten liefern.

Autor
Lydia
Erschienen
Lesedauer
13 Min. Lesezeit
Aktualisiert
Ein erwachsener Mann beugt sich zum Okular eines Teleskops, neben ihm lächelt eine Frau im roten Mantel an einer warm beleuchteten Tür.

Ein Nutzer schreibt Rowan, einer fiktiven erwachsenen Begleiterfigur: „Zeig mir heute Abend die Sternwarte.“ Das Chatmodell kann in der Rolle antworten, aber deine Anwendung muss das Bild erzeugen, die Szene konsistent halten und ein echtes Asset anhängen, sobald eines existiert. Halte den Chat reaktionsschnell, während Bild- und Videojobs separat laufen.

Gib dem Nutzer die Kontrolle über zwei Käufe:

  • Das Standbild freigeben: Zeige eine kostenlose Szenenkarte, frage den Preis des Bildes ab und erstelle es erst, nachdem der Nutzer den Preis akzeptiert hat.
  • Den Clip freigeben: Zeige das fertige Standbild, frage den Preis für dessen Animation ab und erstelle das Video erst, nachdem der Nutzer dieses Bild und den Preis akzeptiert hat.

Speichere jede Anfrage vor dem Absenden, verwende für jede Stufe einen eigenen Schlüssel und liefere aus deiner eigenen gespeicherten Kopie aus. Ein Tool-Aufruf startet die Szenenkarte; er autorisiert nie einen Kauf und kündigt nie einen Anhang an, den es nicht gibt.

Als konkretes Budget: Eine Qwen-Bearbeitung in 1k ($0.036) plus ein fünfsekündiger Seedance-2.5-Clip in 720p ergeben $1.4225, zuzüglich Gebühren für zusätzliche Referenzbilder, Chat-Tokens und Wiederholungen. Frage ein Angebot (Quote) ab, bevor du einem Nutzer einen Preis zeigst.

Der gesamte Ablauf in der richtigen Reihenfolge:

text
1. Chat model calls request_scene_image(scene, details)
2. Server validates owner and scene -> free draft card -> tool result back to the chat
3. Gate 1: user approves the still's price -> Qwen edit task (own idempotency key)
4. Callback or poll -> still ready -> copy it to your own storage
5. Gate 2: user accepts the still -> quote the clip -> user approves its price
6. Seedance task (own key) -> callback or poll -> ready_for_review
7. Review the whole clip -> copy to your storage -> delivered

Aus einer Chatanfrage ein Bild machen, das die App wirklich erzeugen kann

Ein Nutzer schreibt vielleicht „Schick mir noch ein Foto im grünen Mantel“, während bereits ein Video im marineblauen Mantel entsteht. Die Chatantwort und die Medienanfrage sollten sich nicht eine veränderliche Beschreibung teilen. Die App muss wissen, welche fiktive erwachsene Person, welcher Mantel, welcher Schauplatz gemeint ist und ob der Nutzer ein Standbild oder einen Clip will. Sonst kann „noch ein“ versehentlich ein neues Gesicht erzeugen, eine veraltete Szene wiederverwenden oder ein Video kaufen, obwohl der Nutzer ein Foto wollte.

Halte die dauerhaften Figurendaten klein: das freigegebene Porträt, die Volljährigkeit, stabile Aussehensnotizen und die vom Nutzer gewählte Figur. Halte die aktuelle Szene getrennt: Mantel, Raum, Kameraansicht und gewünschte Handlung. Die Unterhaltung kann eine Szene vorschlagen, aber deine Anwendung löst die gespeicherte Figur und die freigegebenen Quell-Assets auf. Lass das Chatmodell weder eine Referenz-URL oder Task-ID erfinden noch behaupten, ein Bild existiere.

Wandle die Anfrage im Sternwarten-Beispiel vor dem Angebot in eine ausdrückliche Szenenbeschreibung um: Rowan, das freigegebene Porträt, marineblauer Mantel, Sternwartenbalkon, halbnahe Einstellung, ein Standbild. Für eine spätere Bitte „Lass es sich bewegen“ nimmst du das akzeptierte Sternwarten-Standbild und eine kleine Kopfdrehung. Schicke nicht die ganze Unterhaltung an das Bildmodell; unzusammenhängende Beschreibungen anderer Figuren oder früherer Outfits können mit der aktuellen Anfrage kollidieren.

Die Oberfläche kann eine kompakte Szenenzusammenfassung zeigen, während das Bild entsteht. Wenn ein Nutzer Grün in Marineblau ändert, mache daraus eine neue Revision und kennzeichne das frühere ausstehende Ergebnis entsprechend. Eine fertige alte Revision kann in seinem privaten Verlauf bleiben, sollte aber nicht stillschweigend als Antwort auf die neueste Anfrage erscheinen.

Prüfe bei einem NSFW-Begleiterprodukt die Eignung für Bild und Video getrennt. Dass eine Bildaufgabe Inhalte für Erwachsene akzeptiert, heißt nicht, dass das nächste Videomodell dieselbe Szene akzeptiert. Sieh dir für jede Stufe die Seite des gewählten Modells an und zeige eine nicht verfügbare Aufgabe ehrlich an, statt einen Clip für Erwachsene zu versprechen, den das gewählte Modell nicht unterstützt.

Zuerst im Playground ausprobieren

Öffne den Qwen Image 2.1 edit Playground. Wähle unter Images die Option Add a file und lade das freigegebene Figurenporträt hoch, beschreibe die neue Szene in Prompt (der Prompt unten funktioniert), setze Resolution auf 1k und Aspect ratio auf 16:9, drücke Run model und beurteile das Standbild. Gib diese Komposition frei und öffne dann den Seedance 2.5 image-to-video Playground: Füge das Standbild als Input image hinzu, beschreibe die Bewegung in Prompt und drücke erneut Run model. Closing frame ist optional.

Den Chat Medien per Tool-Aufruf anfordern lassen

Gib dem Chatmodell ein Tool request_scene_image, das deine Anwendung bittet, eine Szenenkarte vorzubereiten. Verwende den angemeldeten Nutzer und das freigegebene Figurenporträt aus deiner Anwendung, nicht IDs, die in der Chatantwort erfunden wurden.

  • Das Chatmodell: beschreibt die Szene und fragt, ob der Nutzer sie erzeugen lassen will.
  • Deine Anwendung: prüft den Besitz der Figur, zeigt den Preis und wartet auf die Freigabe.
  • Die Medienaufgabe: erzeugt das Bild erst nach der Freigabe; die Animation ist ein separater Kauf.

Grok 4.6 chat unterstützt Tool-Aufrufe im OpenAI-Format. Die API speichert keinen Gesprächsverlauf, also sende die Figurenkarte und den Kontext für fiktionales Schreiben mit jeder Anfrage erneut; die Chat-Rezepte zeigen das Format der Tool-Nachricht.

json
{
  "type": "function",
  "function": {
    "name": "request_scene_image",
    "description": "Ask the app to prepare a picture of the character. Call only when the user asks to see something. The app shows a free scene card first; nothing is made or charged until the user approves a quoted price.",
    "parameters": {
      "type": "object",
      "properties": {
        "scene": {"type": "string", "enum": ["observatory-balcony", "kitchen-morning", "rooftop-dusk"]},
        "details": {"type": "string", "description": "Clothing, props and mood the user asked for, in one sentence."}
      },
      "required": ["scene"]
    }
  }
}

Beantworte den Aufruf mit einer tool-Nachricht, dass eine Entwurfskarte auf Freigabe wartet, damit das Modell eine ehrliche Zeile schreibt („Hier ist die Szenenkarte, soll ich sie erzeugen?“), statt eine Auslieferung zu behaupten.

Figur und Szene als getrennte Datensätze führen

Halte das freigegebene Porträt getrennt von der gewünschten Szene: ein marineblauer Mantel, ein Sternwartenbalkon und eine kleine Drehung zum Teleskop. Das Porträt verankert die Figur; das akzeptierte Szenenbild wird zum Startbild des Videos. Ersetze das Figurenporträt nicht bei jeder neuen Szene.

Speichere vor dem Absenden einen Datensatz pro Anfrage (das sind Felder deiner Anwendung, keine API-Payload):

json
{
  "mediaRequestId": "scene-104-r1",
  "ownerId": "application-user-id",
  "sceneRevision": 1,
  "referenceAssetId": "approved-rowan-portrait",
  "approvedStillAssetId": null,
  "model": "alibaba/qwen-image-2.1/edit",
  "inputSnapshot": {},
  "idempotencyKey": "<uuid generated for this request>",
  "taskId": null,
  "state": "draft",
  "deliveredAssetId": null
}

Wenn der Nutzer den Mantel auf Grün ändert, lege Revision 2 mit neuem Schlüssel an; bearbeite nie die Anfrage mit dem blauen Mantel, während sie läuft.

Speichere das freigegebene Standbild selbst. Der Ergebnislink einer Aufgabe gilt etwa 20 Minuten und die erzeugte Datei etwa 14 Tage. Kopiere das akzeptierte Standbild also in deinen eigenen Speicher und gib Seedance eine stabile öffentliche HTTPS-URL, die die Bilddatei selbst liefert (ein Link auf eine Freigabeseite schlägt fehl). Wenn du den Qwen-Ergebnislink direkt an die Videostufe weitergibst, funktioniert das nur, bis er abläuft. Verwende das gespeicherte Standbild für Bewegungsrevisionen erneut; wer Porträt und Bewegung gleichzeitig ändert, kann beides schwerer korrigieren.

Bild- und Videoparameter getrennt festlegen

Die Route Qwen Image 2.1 edit akzeptiert einen Prompt und ein bis zehn image_urls. Zusätzliche Referenzbilder erhöhen den Preis für 1k; 2k kostet $0.048 mit ein bis zehn Referenzbildern. Verwende für die erste Szene ein freigegebenes Porträt, 1k, 16:9, PNG und ausgeschaltete Prompt-Erweiterung.

Use the supplied portrait as the identity reference for the same fictional adult Rowan. Create a medium shot on an observatory balcony, wearing a navy coat. Keep both hands relaxed and visible. A telescope rests on its tripod beside Rowan. Leave open space on the right for a small head turn. Warm doorway light, natural night background, no text or extra people.

Vergleiche Gesicht und Outfit mit dem freigegebenen Porträt, bevor du dem Nutzer das Standbild anbietest. Bei Gate 2 akzeptiert der Nutzer dieses Standbild und den Clippreis vor der Animation. Wenn das Teleskop schwebt oder rechts kein Platz für die Bewegung bleibt, behebe das auf der Bildstufe; das Video kann ein schlechtes Startbild nicht reparieren.

Ein fiktiver erwachsener Mann im marineblauen Mantel steht auf einem Sternwartenbalkon, die Hände entspannt, neben einem Teleskop auf einem fest stehenden Stativ.

Illustration eines vorgeschlagenen Startbilds: Die Figur hat Platz für eine kleine Drehung, und das Teleskop ist physisch abgestützt. Das ist keine Qwen-Ausgabe und kein Generierungstest.

Die Route Seedance 2.5 image-to-video nimmt image_url, 4 bis 30 Sekunden, 480p, 720p oder 1080p, einen Audio-Schalter und einen optionalen seed entgegen. aspect_ratio akzeptiert nur adaptive, der Clip folgt also der Form des Standbilds; beschneide das Standbild vorher auf dein Ausgabeformat. Die Route reference-to-video hat einen anderen Vertrag, mische ihre Felder also nicht hinein.

Am Entscheidungspunkt ein Angebot abfragen

Qwen-Bearbeitungen kosten laut Preisliste $0.036 für 1k, $0.048 für 2k und $0.072 für 1.5k, jeweils mit einem Referenzbild inklusive. Weitere Referenzbilder erhöhen die Preise für 1k und 1.5k. Seedance kostet $0.1234 pro Sekunde bei 480p, $0.2773 bei 720p und $0.6823 bei 1080p, mit oder ohne Audio zum gleichen Satz.

Verwende das akzeptierte Standbild bei einer Wiederholung der Bewegung erneut.

PlanKosten
Nur 1k-Standbild$0.036
Standbild + 5-s-Clip in 480p$0.653
Standbild + 5-s-Clip in 720p$1.4225
Standbild + 5-s-Clip in 1080p$3.4475
Standbild + zwei 720p-Clipversuche$2.809

Darin ist ein Referenzbild in der 1k-Bearbeitung enthalten; nicht enthalten sind Gebühren für zusätzliche Referenzbilder, Chat-Tokens und weitere Standbildbearbeitungen.

Zeige den Standbildpreis vor der Generierung und den Clippreis, nachdem der Nutzer das Standbild akzeptiert hat, nicht bei jedem Chatbeitrag. Ein Angebot ist kostenlos, reserviert nichts und bleibt fünf Minuten für dieses Konto, diesen API-Schlüssel, dieses Modell und diese Eingabe gültig; ändere Dauer, Auflösung oder Referenzen, brauchst du ein neues. Eine abgesendete Aufgabe hält ihre geschätzten Kosten sofort zurück, und diese Reservierung ist der Höchstbetrag, der abgebucht werden kann. Wer das freigegebene Bild wiederverwendet, hält eine Bewegungsrevision bei den Kosten der Videostufe.

Ein Client für die Videostufe, der eine verlorene Antwort übersteht

Installiere spicyapi mit python -m pip install spicyapi, setze SPICY_API_KEY und APPROVED_STILL_URL und führe das hier aus, nachdem du das Standbild freigegeben hast. Behalte die gespeicherte Run-Datei, um denselben Versuch fortzusetzen. Für eine andere kreative Variante verwende einen neuen Run-Namen.

python
import json
import os
import time
import uuid
from pathlib import Path
from spicyapi import SpicyClient, output_assets

client = SpicyClient()

def run(model, inputs, name):
    path = Path(name + ".json")
    request = {"model": model, "input": inputs}
    saved = json.loads(path.read_text()) if path.exists() else {"request": request, "key": str(uuid.uuid4())}
    if saved["request"] != request:
        raise ValueError("Changed input needs a new run name; preserve the old attempt.")
    def save():
        temporary = path.with_suffix(".tmp")
        temporary.write_text(json.dumps(saved))
        temporary.chmod(0o600)
        temporary.replace(path)
    if not saved.get("taskId"):
        if time.time() - saved.get("sentAt", time.time()) >= 23 * 3600:
            raise RuntimeError("Reconcile the saved attempt before submitting again.")
        if "quote" not in saved:
            saved["quote"] = client.quote_task(model=model, input_data=inputs)
            if input(f"Maximum ${saved['quote']['maxCharge']}. Create? [y/N] ") != "y":
                raise SystemExit("Stopped without creating this task.")
        saved.setdefault("sentAt", 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()
    print("Task:", saved["taskId"])
    for _ in range(360):
        task = client.get_task(saved["taskId"])
        if task["state"] in {"failed", "expired", "canceled"}:
            raise RuntimeError(str(task.get("errorMessage", task["state"])))
        if task["state"] == "succeeded":
            assets = output_assets(task)
            if assets and assets[0].get("unavailable"):
                raise RuntimeError("Output unavailable; keep the task ID for reconciliation.")
            if assets and assets[0].get("url") and not assets[0].get("pending"):
                return assets[0]
        time.sleep(5)
    raise TimeoutError("Rerun the same saved attempt to resume; no new task is needed.")

asset = run("bytedance/seedance-2.5/image-to-video", {
    "image_url": os.environ["APPROVED_STILL_URL"],
    "prompt": "The adult character slowly turns toward the telescope. Hold the camera steady. Keep the navy coat and balcony unchanged.",
    "duration_seconds": 5, "resolution": "720p", "aspect_ratio": "adaptive", "generate_audio": True,
}, "companion-video")
print(asset["url"])

Das Python SDK und die Task-Dokumentation decken die Integration für den Produktivbetrieb ab.

Eine Update-Funktion für Polling und Callbacks

Verwende einen Handler deiner Anwendung für ein abgefragtes Ergebnis und für ein verifiziertes Abschlussereignis. Er sollte das Asset einmal anhängen, es privat halten und den Chat erst benachrichtigen, wenn es zur Prüfung bereit ist. Ein doppeltes Ereignis darf keinen weiteren Galerieeintrag erzeugen.

Sende bei einer Live-Begleiter-App den Abschluss an einen verifizierten Webhook und behalte einen periodischen Statusabgleich für Callbacks, die nie eintreffen. Callback-Bodys enthalten den Prompt und signierte Ergebnislinks, behandle sie also als privat; die Task-Dokumentation beschreibt die Einrichtung. Webhooks mit Abgleichslauf (siehe den Artikel „async-task-api-polling-vs-webhook“) eignen sich für den Produktivbetrieb; für einen Prototyp genügt Polling. Verwende begrenzte Verteilung, wenn viele Nutzer gleichzeitig Medien anfordern.

Auslieferungszustände, Fehler und Abbruch

Verwende draft, submitted, generating, ready_for_review, delivered, failed und rejected. Das API-succeeded bedeutet, dass die Ergebnisverarbeitung beginnt; es heißt nicht, dass der Nutzer das Ergebnis sieht. Prüfe den ganzen Clip auf Gesichtsdrift, bewegte Requisiten und nicht angeforderte Schnitte, bevor er delivered wird.

API-ErgebnisWas zu zeigen istAbbuchung
succeeded, Asset bereitprüfen, dann ausliefernsettled und cost lesen
succeeded, Asset pending„wird noch vorbereitet“, in wenigen Sekunden erneut abfragennoch nicht endgültig
succeeded, Asset unavailableeine bereits gespeicherte Kopie wiederherstellen oder bewusst eine neue Generierung anbietencost lesen
failed oder expiredklare Fehlermeldung und ein Button zum WiederholenReservierung normalerweise freigegeben; einige Inhaltsablehnungen werden berechnet, wenn die Modellseite das angibt

Ein abgelaufenes Angebot braucht ein neues Angebot und eine neue Freigabe; stelle eine bereits akzeptierte Anfrage mit ihrem ursprünglichen Schlüssel wieder her. Eine akzeptierte Aufgabe lässt sich nicht abbrechen. Wenn der Nutzer sagt „vergiss es“, blende den ausstehenden Anhang aus und lass den Job zu Ende laufen; versprich nicht, dass das Schließen des Chats die Abbuchung freigibt. Eine Wiederholung ist eine neue Anfrage mit eigenem Angebot.

Private Begleitermedien privat halten

Prüfe vor der Auslieferung, dass eine Szene dem angemeldeten Nutzer gehört. Kopiere akzeptierte Medien in einen Speicher mit eigenen Zugriffsregeln; temporäre Ergebnislinks sollten keine öffentlichen Galerieadressen werden.

Behandle beim Löschen die Plattformkopie und deine eigene Kopie getrennt. Verkürze mit der Aufbewahrungseinstellung pro Aufgabe, wie lange die Plattform ein Ergebnis behält, und vernichte, wenn ein Nutzer eine Szene oder sein Konto löscht, den gespeicherten Inhalt jeder fertigen Aufgabe (der Abrechnungsdatensatz bleibt). Eine Aufbewahrung von 0 kann das Ergebnis entfernen, bevor dein Worker es kopiert. Lösche deine eigene Kopie und entziehe ihr den Auslieferungszugriff separat; Details stehen in der Dokumentation zur Aufbewahrung. Nutze Aufbewahrungssteuerung und eine dauerhafte Speicher-Pipeline, die zum Produkt passen.

Vier Fehlerfälle mit Fixtures testen

Simuliere einen Doppelklick, eine verlorene Annahmeantwort, ein doppeltes Erfolgsereignis und ein nicht verfügbares Asset. Du solltest eine logische Anfrage erhalten, die Wiederherstellung mit dem ursprünglichen Schlüssel, einen einzigen Galerieanhang und einen ehrlichen Zustand „nicht verfügbar“. Keiner dieser Tests braucht eine kostenpflichtige Generierung. Ob das Gesicht über Standbilder und Clips hinweg konsistent bleibt, ist eine visuelle Frage, also prüfe jede Ausgabe. Lass nicht zu, dass eine Chatnachricht „Dein Video ist fertig“ den echten Task- oder Asset-Zustand überstimmt. Die Sternwarten-Illustration ist eine redaktionelle Szene, keine Ausgabe von Qwen oder Seedance.

Einen Agent den Aufruf übernehmen lassen

Nutze einen Agent, wenn er die Eingaben vorbereiten und das Ergebnis zurückgeben soll, während du Preis und kreative Entscheidungen freigibst.

  • CLI: Mit einer input.json wie {"image_urls": ["<approved portrait URL>"], "prompt": "<the scene prompt above>", "resolution": "1k", "aspect_ratio": "16:9", "output_format": "png", "enable_prompt_expansion": false} führst du npx @spicyapi/cli tasks create --model alibaba/qwen-image-2.1/edit --input-file input.json --wait aus. Die CLI bestätigt den Preis; behalte die zurückgegebene Task-ID für die Wiederherstellung.
  • MCP: Bitte deinen Assistenten, spicyapi_task_create für alibaba/qwen-image-2.1/edit und dann spicyapi_task_wait zu verwenden. Der MCP-Server verlangt vor dem Erstellen eine Preisbestätigung.
  • Skill: Installiere ihn mit npx skills add spicyapi-ai/spicy-skill und bitte dann $spicyapi, denselben Ablauf auszuführen und für deine Freigabe anzuhalten. Der Skill nutzt eine konfigurierte CLI- oder MCP-Verbindung.

Auch in English · 日本語 · 한국어 · Français · Español · Português (Brasil) · Русский · Italiano · Polski

Selbst ausprobieren

Sie zahlen nur für das, was Sie ausführen – ohne Tarif, ohne Abo.

Erstellt mit MiniMax H3 · ByteDance Seedance 2.0 Fast