Randări
Randări codai Studio — transformi o compoziție HyperFrames HTML (cu asset-urile tale Studio) într-un MP4, apoi interoghezi până când e gata.
O randare transformă un video descris în HTML într-un MP4. Trimiți o compoziție HyperFrames, gateway-ul o pune în coada worker-ului de randare, iar tu interoghezi până când video-ul e gata. Id-ul public al modelului este codai-render.
| Metodă | Cale | Rezultat |
|---|---|---|
POST | /v1/studio/renders | 202 — randarea e în coadă; întoarce id-ul și prețul |
GET | /v1/studio/renders/{id} | Starea, progress și asset-ul MP4 după succeeded |
Compoziții HyperFrames
O compoziție este un document HTML complet (index.html, până la 2.000.000 de caractere), așezat pe o linie de timp cu atribute data-*:
- elementul rădăcină are
data-composition-id,data-widthșidata-height(pânza, în pixeli); - fiecare clip (text, imagine, video, audio, orice element) are
data-startșidata-durationîn secunde și undata-track-index(pistele mai mari se desenează deasupra); - animațiile CSS și JavaScript rulează așa cum sunt scrise. GSAP este permis, la fel Lottie, Three.js și anime.js — URL-urile lor de CDN sunt servite din copii locale în randator, așa că un URL de CDN obișnuit funcționează.
Ieșirea durează până la finalul ultimului clip (data-start + data-duration), cel mult 180 de secunde.
<div data-composition-id="launch" data-width="1920" data-height="1080" style="background:#0b0b10">
<img class="clip" src="asset:3f2b8c1e-0000-4000-8000-000000000001"
data-start="0" data-duration="8" data-track-index="0" style="width:100%;height:100%;object-fit:cover" />
<h1 id="title" class="clip" data-start="1" data-duration="6" data-track-index="1">codai Studio</h1>
<audio class="clip" src="asset:3f2b8c1e-0000-4000-8000-000000000002"
data-start="0" data-duration="8" data-track-index="2"></audio>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script>gsap.from('#title', { opacity: 0, y: 40, duration: 1, delay: 1 });</script>
</div>Folosirea asset-urilor Studio
Referă orice asset al tău ca asset:<asset_id> într-un src, href sau url(...) din CSS — imagini generate, clipuri muzicale, video-uri, vorbire sau fișiere încărcate. Înainte de a pune randarea în coadă, gateway-ul verifică faptul că fiecare asset referit există, îți aparține și este ready; altfel cererea eșuează cu 400 asset_not_found și nu se taxează nimic. Cel mult 50 de asset-uri distincte per randare.
Pentru lovituri, whoosh-uri și alte efecte sonore, importă gratuit un sunet CC0 din biblioteca de efecte sonore și referă asset-ul rezultat la fel.
Pentru video-uri narate, creează vocea cu POST /v1/audio/speech și { "model": "codai-speech", "store": true, "timestamps": true }: asset.asset_id din răspuns intră în <audio src="asset:<id>">, iar words ([{ word, start, end }]) îți dă momentele pentru subtitrări (vezi text-to-speech).
variables (opțional) este un obiect cu variabilele compoziției HyperFrames, pentru șabloane care citesc valori la randare.
Cererea
{
"model": "codai-render",
"html": "<div data-composition-id=\"launch\" data-width=\"1920\" data-height=\"1080\">…</div>",
"aspect_ratio": "16:9",
"quality": "final",
"fps": 30,
"title": "Launch teaser"
}| Câmp | Implicit | Semnificație |
|---|---|---|
html | — | Compoziția (obligatoriu). |
model | codai-render | Singurul model de randare. |
aspect_ratio | 16:9 | 16:9 (1920×1080), 9:16 (1080×1920) sau 1:1 (1080×1080). Trebuie să se potrivească cu data-width/data-height ale rădăcinii, altfel 400. |
quality | final | final sau draft (vezi mai jos). |
fps | 30 | 24, 25 sau 30; doar la final. |
variables | — | Variabilele compoziției HyperFrames. |
title | — | Până la 200 de caractere, stocat cu asset-ul. |
ai_label | automat | corner, end_card sau none — eticheta IA vizibilă (vezi mai jos). |
Limite
- cel mult 180 de secunde de ieșire;
- cel mult 1080p (cele trei rapoarte de aspect de mai sus);
- 24, 25 sau 30 fps;
- 3 randări în coadă sau în lucru per utilizator în același timp — a patra primește
429 too_many_renders.
Draft sau final
draft | final | |
|---|---|---|
| Cadre pe secundă | 15 fps | 24 / 25 / 30 fps |
| Rezoluție | 540p pe latura scurtă | 1080p complet |
| Normalizarea volumului | nu | la −14 LUFS integrat, −1 dBTP |
| Preț | jumătate | întreg |
Iterezi cu draft, apoi randezi ca final versiunea pe care o păstrezi.
Eticheta IA (Regulamentul UE privind IA, art. 50 alin. 4)
ai_label imprimă în MP4 o etichetă vizibilă:
| Valoare | Efect |
|---|---|
corner | O etichetă mică „Generat cu IA” (pentru conținut în engleză: „AI-generated”) în colțul din dreapta jos, pe toată durata videoclipului. |
end_card | Un cadru final de circa 2 s cu eticheta. |
none | Fără etichetă. |
Dacă lipsește, eticheta este corner când randarea folosește narațiune TTS (o voce sintetică) sau imagini generate cu IA și none în rest. Un none explicit la o randare cu narațiune TTS este refuzat cu 400 ai_label_required. Fiecare răspuns de randare (202 și GET) raportează valoarea rezolvată în ai_label; aceeași opțiune există la randarea creațiilor și la loturi (ai_label se aplică randării fiecărui rând).
Regulamentul UE privind IA, art. 50 alin. 4. Dacă publici un videoclip generat sau manipulat cu IA care seamănă cu persoane, locuri sau evenimente reale (un deepfake), tu — ca implementator — ești obligat să dezvălui că acesta a fost generat artificial. Eticheta te ajută să faci asta, dar nu îți înlocuiește obligația. Vezi Politica de utilizare acceptabilă.
Starea și QA
GET /v1/studio/renders/{id} întoarce queued, rendering (cu progress între 0 și 1), succeeded sau failed.
{
"id": "9c1d2e3f-0000-4000-8000-000000000010",
"object": "studio.render",
"status": "succeeded",
"progress": 1,
"quality": "final",
"aspect_ratio": "16:9",
"fps": 30,
"error": null,
"qa": { "duration_seconds": 31.2, "width": 1920, "height": 1080, "lufs": -14.1, "warnings": [] },
"cost_micro_usd": "320000",
"data": [{ "asset_id": "9c1d2e3f-0000-4000-8000-000000000010", "kind": "render", "url": "https://storage.googleapis.com/…", "mime_type": "video/mp4", "…": "…" }],
"poster_url": "https://storage.googleapis.com/…poster.jpg…"
}După succes, data conține MP4-ul ca un singur obiect asset (kind: "render", URL semnat valabil o oră), iar poster_url un cadru-poster JPEG. id-ul randării este și asset_id-ul ei, așa că apare în GET /v1/assets?kind=render și poți obține oricând un URL proaspăt cu GET /v1/assets/{id}.
Fiecare randare trece printr-o verificare tehnică QA. O compoziție care nu trece de lint, iese mai lungă decât limita, are dimensiuni greșite sau e aproape complet neagră ori înghețată eșuează — error are forma <cod>: <mesaj>, cu coduri precum lint_failed, render_failed, too_long, dimension_mismatch, black_video, frozen_video sau timeout. Problemele mai mici nu opresc randarea; apar în qa.warnings:
| Avertisment | Semnificație |
|---|---|
no_audio | Video-ul nu are pistă audio. |
silent_audio | Există o pistă audio, dar e tăcută. |
lufs_off_target | Volumul perceput e la mai mult de 1 LU de −14 LUFS după normalizare. |
Cronologii de scene
POST /v1/studio/timelines construiește un video dintr-o listă de scene, fără să scrii HTML. Fiecare scenă este una dintre imaginile sau video-urile tale ready, afișată pe tot cadrul; imaginile primesc un zoom lent, iar video-urile își păstrează sunetul propriu la video_volume.
{
"title": "Teaser de produs",
"aspect_ratio": "16:9",
"quality": "draft",
"transition": "fade",
"music_asset_id": "3f2b8c1e-0000-4000-8000-000000000003",
"narration_defaults": { "model": "codai-speech", "language": "ro-RO" },
"scenes": [
{ "id": "intro", "kind": "image", "asset_id": "3f2b8c1e-0000-4000-8000-000000000001", "seconds": 4,
"prompt": "A sunrise over a mountain lake", "narration": { "text": "Fiecare dimineață începe undeva." } },
{ "kind": "video", "asset_id": "3f2b8c1e-0000-4000-8000-000000000002", "seconds": 6 }
]
}| Câmp | Observații |
|---|---|
scenes | 1–30 de scene, cel mult 180 s în total (timeline_too_long). kind trebuie să corespundă tipului MIME al asset-ului. |
scenes[].id | Opțional, ^[a-z0-9-]{1,32}$, implicit s1…sN. Rămâne același de la o regenerare la alta. |
scenes[].seconds | 2–30 s (implicit 5). O scenă narată durează cel puțin cât narațiunea plus 0,6 s. |
scenes[].fit | cover (implicit, cu decupare) sau contain (cu benzi negre). |
scenes[].narration | Sintetizată acum cu narration_defaults.model și taxată per apel (narration_cost_micro_usd). Nu este disponibilă pentru conturile cu rezidența datelor în UE (403 residency_unavailable). |
transition | fade (implicit, estompare de 0,5 s) sau none. |
music_asset_id, music_volume | Unul dintre fișierele tale audio, sub întreaga cronologie (volum 0–1, implicit 0,126). |
Răspunsul este 202, cu id, root_render_id, scene_count, duration_seconds și prețul. Interoghează GET /v1/studio/renders/{id} ca la orice randare; randarea se taxează la fel, o singură dată, după succes. Starea unei randări cu cronologie conține și timeline (scenele, fiecare cu asset și narration_asset), parent_render_id și root_render_id. Adaugă ?expand=assets ca să primești URL-uri semnate pentru asset-urile scenelor; fără el, url este null.
Un asset necunoscut, al altcuiva, neterminat sau de tip greșit întoarce 400 asset_not_found, iar id-urile problematice sunt listate în error.details.missing.
Regenerarea unei scene
POST /v1/studio/renders/{id}/scenes/{sceneId}/regenerate creează o randare nouă în care o singură scenă este înlocuită. Randarea sursă rămâne neschimbată; cea nouă are parent_render_id egal cu sursa și păstrează root_render_id, așa că GET /v1/studio/renders?root_render_id=… listează toate versiunile. Sursa trebuie să fie o randare cu cronologie reușită (altfel 409 not_a_timeline).
| Corp | Efect | Cost |
|---|---|---|
{"visual": {"asset_id": "…"}} | Folosește în schimb una dintre imaginile sau video-urile tale. | niciunul |
{"visual": {"prompt": "…"}} pe o scenă cu imagine | Imagine nouă cu codai-image, în raportul de aspect al cronologiei. | taxat acum |
{"visual": {"prompt": "…", "seconds": 6}} pe o scenă video | Clip nou codai-video (4, 6 sau 8 s, implicit 8). Randarea rămâne queued cu stage: "generating_scene" până când un poll vede clipul. | taxat o singură dată, când clipul e gata |
{"narration": {"text": "…"}} | Narează din nou scena. | taxat acum |
{"narration": {"asset_id": "…"}} | Folosește unul dintre fișierele tale audio ca narațiune. | niciunul |
{"narration": null} | Elimină narațiunea. | niciunul |
Dacă omiți prompt sau text, se refolosește valoarea stocată în scenă. Răspunsul 202 întoarce scene_cost_micro_usd (generarea taxată până acum) alături de prețul randării; noua randare se taxează după succes. Video-ul și narațiunea nu sunt disponibile pentru conturile cu rezidența datelor în UE.
GET /v1/studio/renders listează randările tale, cele mai noi primele (before, limit 1–100, root_render_id), fără să le interogheze și fără URL-uri semnate.
Prețuri
Se taxează o singură dată, la primul poll care vede randarea terminată, pe secundă de ieșire (rotunjit în sus la secunde întregi; până la 50 ms de padding al encoderului nu se taxează):
| Calitate | Pe secundă de ieșire | Minimum |
|---|---|---|
final | 0,01 $ | 0,05 $ |
draft | 0,005 $ | 0,025 $ |
O randare final de 31,2 s costă 32 × 0,01 $ = 0,32 $. Randările eșuate nu costă nimic. Răspunsul 202 repetă prețul în price_micro_usd_per_second și minimum_micro_usd; costul apare în cost_micro_usd după succes. Conturile cu portofel plătesc aceeași sumă, convertită ca la celelalte servicii media (vezi Limite și prețuri).
Erori
| HTTP | code | Când |
|---|---|---|
| 400 | bad_request | Corp invalid sau aspect_ratio nu se potrivește cu data-width/data-height ale rădăcinii. |
| 400 | asset_not_found | Un asset referit e necunoscut, nu e al tău sau nu e ready. |
| 400 | timeline_too_long | O cronologie (sau o versiune regenerată) ar dura mai mult de 180 s. |
| 400 | ai_label_required | ai_label: "none" la o randare cu narațiune TTS (vocea sintetică trebuie etichetată). |
| 403 | forbidden | Cheia nu are voie să folosească codai-render. |
| 403 | residency_unavailable | S-a cerut narațiune sau o scenă video generată pe un cont cu rezidența datelor în UE. |
| 402 / 429 | subscription_inactive / rate_limit_exceeded | Abonament inactiv sau plafon de cheltuieli atins. |
| 429 | too_many_renders | Ai deja 3 randări în coadă sau în lucru. |
| 404 | not_found | Id de randare necunoscut sau randarea altui utilizator. |
| 409 | not_a_timeline | Regenerare de scenă pe o randare care nu s-a terminat cu succes sau nu are cronologie. |
| 503 | renders_unavailable | Randările nu sunt configurate pe acest gateway. |
Exemple
curl
ID=$(curl -s https://ai.codai.ro/v1/studio/renders \
-H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
-d @render.json | jq -r .id)
while :; do
R=$(curl -s "https://ai.codai.ro/v1/studio/renders/$ID" -H "Authorization: Bearer $CODAI_API_KEY")
S=$(echo "$R" | jq -r .status)
echo "$S $(echo "$R" | jq -r .progress)"
[ "$S" = succeeded ] || [ "$S" = failed ] && break
sleep 5
done
curl -L -o render.mp4 "$(echo "$R" | jq -r '.data[0].url')"SDK TypeScript
import { readFile } from 'node:fs/promises';
import { Codai } from 'codai-sdk';
const client = new Codai({ apiKey: process.env.CODAI_API_KEY! });
const html = await readFile('index.html', 'utf8');
const r = await client.renders.render(
{ html, aspect_ratio: '16:9', quality: 'draft' },
{ pollIntervalMs: 5000, timeoutMs: 15 * 60_000 },
);
console.log(r.asset.url, r.qa?.warnings, r.costMicroUsd);client.renders.create(body) și client.renders.retrieve(id) sunt cele două apeluri brute; render() interoghează până la succeeded și aruncă o eroare la failed sau la expirarea timpului.
Cronologii și regenerarea scenelor:
const job = await client.renders.createTimeline({
quality: 'draft',
scenes: [
{ id: 'intro', kind: 'image', asset_id: imageId, seconds: 4, narration: { text: 'Bună ziua.' } },
{ kind: 'video', asset_id: videoId },
],
});
// …interoghează client.renders.retrieve(job.id) până la succeeded, apoi:
const v2 = await client.renders.regenerateScene(job.id, 'intro', { visual: { prompt: 'The same lake at dusk' } });
const versions = await client.renders.list({ rootRenderId: job.root_render_id });
const detail = await client.renders.retrieve(v2.id, { expandAssets: true });SDK Python
import os
from pathlib import Path
from codai import Codai
client = Codai(api_key=os.environ["CODAI_API_KEY"])
r = client.renders.render(
{"html": Path("index.html").read_text(encoding="utf-8"), "aspect_ratio": "9:16", "quality": "final"},
poll_interval=5,
timeout=900,
)
print(r.asset["url"], r.qa["warnings"], r.cost_micro_usd)render() ridică RuntimeError când randarea a eșuat și TimeoutError când nu se termină la timp; create() și retrieve() întorc JSON-ul brut.
Și cronologiile, și regenerarea scenelor întorc JSON-ul brut:
job = client.renders.create_timeline({
"quality": "draft",
"scenes": [
{"id": "intro", "kind": "image", "asset_id": image_id, "seconds": 4, "narration": {"text": "Bună ziua."}},
{"kind": "video", "asset_id": video_id},
],
})
# …interoghează client.renders.retrieve(job["id"]) până la succeeded, apoi:
v2 = client.renders.regenerate_scene(job["id"], "intro", {"narration": None})
versions = client.renders.list(root_render_id=job["root_render_id"])
detail = client.renders.retrieve(v2["id"], expand_assets=True)Asset-uri
Conținutul media codai Studio stocat — listezi, obții un URL semnat proaspăt, descarci sau ștergi imaginile, muzica și video-urile generate.
Documente
Documente codai Studio — construiești prezentări (PPTX), documente (DOCX) și foi de calcul (XLSX) dintr-un spec JSON, le previzualizezi ca PDF și PNG și transformi prezentările în MP4-uri narate.