lightricks/ltx-2-19b-lora/image-to-videoOd 14 kredytów za klip 5-sekundowy· Obraz w wideoObraz na wideo w LTX-2 19B LoRA animuje twoje zdjęcie z maksymalnie trzema twoimi plikami LoRA i ścieżką stereo, od 5 do 20 sekund w 480p, 720p lub 1080p, w kształcie zdjęcia.
Wywołujesz go nazwą modelu lightricks/ltx-2-19b-lora/image-to-video w POST /jobs/createTask, od 14 kredytów za klip 5-sekundowy (0,14 USD). Nowe konta dostają 30 darmowych kredytów, więc możesz najpierw przetestować go w Playgroundzie.
- Utwórz wideo
- Obraz w wideo
- Do 1080p
- Klipy 5–20 s
- Własne LoRA
lightricks/ltx-2-19b-lora/image-to-videoPOST https://api.popnow.ai/api/v1/jobs/createTask# 1. Create the task. Keep the Idempotency-Key if you retry after a network error.
export IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://api.popnow.ai/api/v1/jobs/createTask \
-H "Authorization: Bearer $POPNOW_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"model": "lightricks/ltx-2-19b-lora/image-to-video",
"input": {
"prompt": "A slow aerial shot drifting over a misty pine forest at sunrise",
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"resolution": "720p",
"duration_seconds": 5
}
}'
# 2. Copy data.taskId from the response, then poll until state is succeeded, failed, canceled or expired.
TASK_ID="TASK_ID_FROM_RESPONSE"
curl "https://api.popnow.ai/api/v1/jobs/recordInfo?taskId=$TASK_ID" \
-H "Authorization: Bearer $POPNOW_API_KEY"// Node 18+ (or any runtime with fetch). Keep the API key on your server.
const BASE = "https://api.popnow.ai/api/v1";
const headers = { Authorization: `Bearer ${process.env.POPNOW_API_KEY}` };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function call(path, init = {}) {
const res = await fetch(BASE + path, { ...init, headers: { ...headers, ...init.headers } });
const body = await res.json();
if (body.code !== 200) throw new Error(`${body.code} ${body.msg}`);
return body.data;
}
const { taskId } = await call("/jobs/createTask", {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({
"model": "lightricks/ltx-2-19b-lora/image-to-video",
"input": {
"prompt": "A slow aerial shot drifting over a misty pine forest at sunrise",
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"resolution": "720p",
"duration_seconds": 5
}
}),
});
// Poll with backoff. A finished task may still be copying files: wait while an asset is pending.
let task;
for (let delay = 2000; ; delay = Math.min(delay * 1.5, 15000)) {
await sleep(delay);
task = await call(`/jobs/recordInfo?taskId=${taskId}`);
const copying = (task.output?.assets ?? []).some((a) => a.pending && !a.unavailable);
if (["succeeded", "failed", "canceled", "expired"].includes(task.state) && !copying) break;
}
if (task.state !== "succeeded") throw new Error(`${task.state}: ${task.errorMessage ?? "no detail"}`);
// Each asset has a signed url (no API key needed) that expires at expiresAt: save the file.
for (const asset of task.output.assets) console.log(asset.url);import os, time, uuid, requests
BASE = "https://api.popnow.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['POPNOW_API_KEY']}"}
def call(method, path, **kwargs):
body = requests.request(method, BASE + path, headers={**HEADERS, **kwargs.pop("headers", {})}, timeout=30, **kwargs).json()
if body["code"] != 200:
raise RuntimeError(f'{body["code"]} {body["msg"]}')
return body["data"]
task_id = call(
"POST",
"/jobs/createTask",
headers={"Idempotency-Key": str(uuid.uuid4())}, # reuse it if you retry
json={
"model": "lightricks/ltx-2-19b-lora/image-to-video",
"input": {
"prompt": "A slow aerial shot drifting over a misty pine forest at sunrise",
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"resolution": "720p",
"duration_seconds": 5
}
},
)["taskId"]
# Poll with backoff. A finished task may still be copying files: wait while an asset is pending.
delay = 2.0
while True:
time.sleep(delay)
task = call("GET", "/jobs/recordInfo", params={"taskId": task_id})
assets = (task.get("output") or {}).get("assets") or []
copying = any(a.get("pending") and not a.get("unavailable") for a in assets)
if task["state"] in ("succeeded", "failed", "canceled", "expired") and not copying:
break
delay = min(delay * 1.5, 15.0)
if task["state"] != "succeeded":
raise RuntimeError(f'{task["state"]}: {task.get("errorMessage", "no detail")}')
# Each asset has a signed url (no API key needed) that expires at expiresAt: save the file.
for asset in assets:
print(asset["url"])input jest zbudowany ze schematu poniżej, więc przechodzi walidację bez zmian. Zamień prompt i linki na własne.
Cena
| Opcja | Kredyty za sekundę | ≈ USD |
|---|---|---|
resolution 480p | 2,7 | 0,027 USD |
resolution 720p | 3,6 | 0,036 USD |
resolution 1080p | 5,4 | 0,054 USD |
1 kredyt = 0,01 USD · Wszystkie opcje i ceny: GET /models/lightricks/ltx-2-19b-lora/image-to-video
Każde zadanie jest zaokrąglane w górę do pełnych kredytów, więc jedno uruchomienie na najniższym poziomie kosztuje 14 kredytów za klip 5-sekundowy (0,14 USD).
Limity
- Długość klipu
- 5–20 s
- Rozdzielczości
- 480p · 720p · 1080p
Parametry
Te pola pochodzą z tego samego schematu, którym sprawdzamy każde żądanie. Pola spoza listy są odrzucane.
promptstringWymaganeDescribe what happens after the opening frame: the action, the camera and the sound.
image_urlstringWymaganeObraz, od którego zaczynasz.
resolutionstringOpcjonalneZmienia cenęRozmiar wyniku. Większy jest ostrzejszy i droższy.
duration_secondsintegerOpcjonalneZmienia cenęDługość klipu. Płacisz za każdą sekundę.
lorasarray<object>OpcjonalneWczytaj własne pliki LoRA, by utrwalić styl lub postać.
Pola wewnętrzne
loras[].pathstringWymaganeDirect URL of a LoRA weights file.
loras[].scalenumberWymaganeWeight strength from zero to four.
seedintegerOpcjonalneTa sama liczba daje powtarzalny wynik.
Surowy JSON Schema
{
"additionalProperties": false,
"properties": {
"duration_seconds": {
"default": 5,
"description": "Requested output length in whole seconds, from 5 to 20.",
"maximum": 20,
"minimum": 5,
"type": "integer"
},
"image_url": {
"contentMediaType": [
"image/jpeg",
"image/png",
"image/webp",
"image/gif"
],
"description": "Opening frame. The clip starts from this image and keeps its aspect ratio.",
"format": "uri",
"type": "string"
},
"loras": {
"default": [],
"description": "Up to three LoRA weight files trained for LTX-2 19B, each with its own strength. Files over 2 GiB are rejected.",
"items": {
"additionalProperties": false,
"properties": {
"path": {
"contentMediaType": [
"application/octet-stream"
],
"description": "Direct URL of a LoRA weights file.",
"format": "uri",
"minLength": 1,
"type": "string"
},
"scale": {
"default": 1,
"description": "Weight strength from zero to four.",
"maximum": 4,
"minimum": 0,
"type": "number"
}
},
"required": [
"path",
"scale"
],
"type": "object"
},
"maxItems": 3,
"type": "array"
},
"prompt": {
"description": "Describe what happens after the opening frame: the action, the camera and the sound.",
"minLength": 1,
"type": "string"
},
"resolution": {
"default": "720p",
"description": "Output resolution.",
"enum": [
"480p",
"720p",
"1080p"
],
"type": "string"
},
"seed": {
"description": "Random seed for controlled comparisons, not a frame-identical guarantee.",
"maximum": 2147483647,
"minimum": 0,
"type": "integer"
}
},
"required": [
"prompt",
"image_url"
],
"type": "object"
}Prawdziwy wynik z LTX-2 19B LoRA
They stay where they are, holding hands and laughing softly; one swirls her bare foot through the shallows and a trail of blue sparks follows it. The camera dollies out slowly from the two of them to reveal the whole dark cave glowing around them and the starlit mouth beyond. Audio: waves booming and echoing in the cave, water lapping, drops falling from the roof, their soft laughter echoing.
{
"model": "lightricks/ltx-2-19b-lora/image-to-video",
"input": {
"prompt": "They stay where they are, holding hands and laughing softly; one swirls her bare foot through the shallows and a trail of blue sparks follows it. The camera dollies out slowly from the two of them to reveal the whole dark cave glowing around them and the starlit mouth beyond. Audio: waves booming and echoing in the cave, water lapping, drops falling from the roof, their soft laughter echoing.",
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"resolution": "720p",
"duration_seconds": 5,
"seed": 510526
}
}Ten wynik powstał z przesłanego pliku: wstaw własny link w miejsce przykładowego.
Odpowiedzi, odpytywanie i callbacki
Wszystkie modele działają tak samo: utwórz zadanie, a potem odpytuj je albo poczekaj na callback.
Najpierw cena
curl -X POST https://api.popnow.ai/api/v1/jobs/quote \
-H "Authorization: Bearer $POPNOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "lightricks/ltx-2-19b-lora/image-to-video",
"input": {
"prompt": "A slow aerial shot drifting over a misty pine forest at sunrise",
"image_url": "https://media.popnow.ai/influencer/examples/marco.webp",
"resolution": "720p",
"duration_seconds": 5
}
}'To samo ciało co createTask. Zwraca przewidywaną i maksymalną możliwą opłatę, niczego nie uruchamiając.
Przyjęte
HTTP/1.1 202 Accepted
{
"code": 200,
"msg": "success",
"data": {
"taskId": "task_01k3m8x9q2z4v7n5p6r8s0t1w3",
"state": "queued"
},
"request_id": "req_example_create"
}HTTP 202 oznacza, że zadanie przyjęto, a kredyty są zarezerwowane, jeszcze nie pobrane. Ostateczna opłata nigdy nie przekroczy rezerwacji.
Sprawdź status
curl "https://api.popnow.ai/api/v1/jobs/recordInfo?taskId=$TASK_ID" \
-H "Authorization: Bearer $POPNOW_API_KEY"Odpytuj co kilka sekund, stopniowo wydłużając odstęp, i przestań, gdy state ma wartość succeeded, failed, canceled lub expired.
Gotowe zadanie
{
"code": 200,
"msg": "success",
"data": {
"taskId": "task_01k3m8x9q2z4v7n5p6r8s0t1w3",
"model": "lightricks/ltx-2-19b-lora/image-to-video",
"state": "succeeded",
"output": {
"assets": [
{
"url": "https://cdn.popnow.ai/tasks/task_01k3m8x9q2z4v7n5p6r8s0t1w3/output.mp4?signature=FROM_RESPONSE",
"expiresAt": "2026-10-17T12:20:00Z",
"mime": "video/mp4",
"width": 1280,
"height": 720,
"durationSeconds": 5
}
]
},
"settled": true
},
"request_id": "req_example_record"
}Pobierz plik z url przed expiresAt. Potrzebujesz później nowego linku? Poproś o niego przez POST /common/download-url.
Weryfikacja callbacku
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
// express.raw keeps the exact bytes that were signed; express.json would not match.
app.post("/hooks/popnow", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.header("X-Webhook-Timestamp");
const signature = req.header("X-Webhook-Signature") ?? "";
const body = JSON.parse(req.body.toString("utf8"));
const taskId = body.data?.taskId ?? body.task_id;
const digest = createHash("sha256").update(req.body).digest("hex");
const expected = createHmac("sha256", process.env.POPNOW_WEBHOOK_SECRET)
.update(`${taskId}.${timestamp}.${digest}`)
.digest("base64");
const ok = expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
// Check the signature first, then reject anything older than five minutes.
if (!ok || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.status(401).end();
res.status(200).end(); // answer fast, then fetch the task and store its files
});Gdy ustawisz callBackUrl, wyślemy ci gotowe zadanie. Zanim mu zaufasz, sprawdź podpis na surowym ciele żądania.
Wysyłanie pliku lokalnego
curl -X POST https://api.popnow.ai/api/v1/common/upload-url \
-H "Authorization: Bearer $POPNOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contentType": "image/png", "bytes": 482113}'
# PUT the file bytes to data.uploadUrl with every header in data.headers, then commit it:
curl -X POST https://api.popnow.ai/api/v1/files/$FILE_ID/commit \
-H "Authorization: Bearer $POPNOW_API_KEY"
# The commit returns a popnow://f/... reference. Put it in any image, video or
# audio field of the task input instead of a public https URL.Publiczne linki https działają bez zmian. Pliki lokalne przechodzą najpierw przez upload-url i commit.

